//+--------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1996 - 1999
//
// File:        backup.cpp
//
// Contents:    Cert Server wrapper routines
//
//---------------------------------------------------------------------------

#include <pch.cpp>

#pragma hdrstop

#include "certdb.h"
#include "cscsp.h"

#include <esent.h>

#define __dwFILE__	__dwFILE_CERTLIB_BACKUP_CPP__


#define _64k		(64 * 1024)


DWORD
_64kBlocks(
    IN DWORD nFileSizeHigh,
    IN DWORD nFileSizeLow)
{
    LARGE_INTEGER li;

    li.HighPart = nFileSizeHigh;
    li.LowPart = nFileSizeLow;
    return((DWORD) ((li.QuadPart + _64k - 1) / _64k));
}


HRESULT
myLargeAlloc(
    OUT DWORD *pcbLargeAlloc,
    OUT BYTE **ppbLargeAlloc)
{
    HRESULT hr;

    // at 512k the server begins doing efficient backups
    *pcbLargeAlloc = 512 * 1024;
    *ppbLargeAlloc = (BYTE *) VirtualAlloc(
				    NULL,
				    *pcbLargeAlloc,
				    MEM_COMMIT,
				    PAGE_READWRITE);
    if (NULL == *ppbLargeAlloc)
    {
        // couldn't alloc a large chunk?  Try 64k...

	*pcbLargeAlloc = _64k;
        *ppbLargeAlloc = (BYTE *) VirtualAlloc(
					NULL,
					*pcbLargeAlloc,
					MEM_COMMIT,
					PAGE_READWRITE);
        if (NULL == *ppbLargeAlloc)
        {
            hr = myHLastError();
	    _JumpError(hr, error, "VirtualAlloc");
        }
    }
    hr = S_OK;

error:
    return(hr);
}


// Files to look for when checking for an existing DB, AND
// Files to delete when clearing out a DB or DB Log directory:
// Do NOT delete certsrv.mdb from Cert server 1.0!

WCHAR const * const g_apwszDBFileMatchPatterns[] =
{
    L"res*.log",
    TEXT(szDBBASENAMEPARM) L"*.log",	// "edb*.log"
    TEXT(szDBBASENAMEPARM) L"*.chk",	// "edb*.chk"
    L"*" wszDBFILENAMEEXT,		// "*.edb"
    NULL
};


HRESULT
myDeleteDBFilesInDir(
    IN WCHAR const *pwszDir)
{
    HRESULT hr;
    WCHAR const * const *ppwsz;

    for (ppwsz = g_apwszDBFileMatchPatterns; NULL != *ppwsz; ppwsz++)
    {
	hr = myDeleteFilePattern(pwszDir, *ppwsz, FALSE);
        _JumpIfError(hr, error, "myDeleteFilePattern");
    }
    hr = S_OK;

error:
    return(hr);
}


HRESULT
DoFilesExistInDir(
    IN WCHAR const *pwszDir,
    IN WCHAR const *pwszPattern,
    OUT BOOL *pfFilesExist,
    OPTIONAL OUT WCHAR **ppwszFileInUse)
{
    HRESULT hr;
    HANDLE hf = INVALID_HANDLE_VALUE;
    WCHAR *pwszFindPattern = NULL;
    WIN32_FIND_DATA wfd;

    *pfFilesExist = FALSE;
    if (NULL != ppwszFileInUse)
    {
	*ppwszFileInUse = NULL;
    }

    hr = myBuildPathAndExt(pwszDir, pwszPattern, NULL, &pwszFindPattern);
    _JumpIfError(hr, error, "myBuildPathAndExt");

    hf = FindFirstFile(pwszFindPattern, &wfd);
    if (INVALID_HANDLE_VALUE == hf)
    {
	hr = S_OK;
	goto error;
    }
    do
    {
	if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	{
	    continue;
	}

	//printf("File: %ws\n", wfd.cFileName);
	*pfFilesExist = TRUE;

	if (NULL != ppwszFileInUse)
	{
	    WCHAR *pwszFile;

	    hr = myBuildPathAndExt(pwszDir, wfd.cFileName, NULL, &pwszFile);
	    _JumpIfError(hr, error, "myBuildPathAndExt");

	    if (myIsFileInUse(pwszFile))
	    {
		DBGPRINT((
		    DBG_SS_CERTLIB,
		    "DoFilesExistInDir: File In Use: %ws\n",
		    pwszFile));

		*ppwszFileInUse = pwszFile;
		hr = S_OK;
		goto error;
	    }
	    LocalFree(pwszFile);
	}

    } while (FindNextFile(hf, &wfd));
    hr = S_OK;

error:
    if (INVALID_HANDLE_VALUE != hf)
    {
	FindClose(hf);
    }
    if (NULL != pwszFindPattern)
    {
	LocalFree(pwszFindPattern);
    }
    return(hr);
}


HRESULT
myDoDBFilesExistInDir(
    IN WCHAR const *pwszDir,
    OUT BOOL *pfFilesExist,
    OPTIONAL OUT WCHAR **ppwszFileInUse)
{
    HRESULT hr;
    WCHAR const * const *ppwsz;

    *pfFilesExist = FALSE;
    if (NULL != ppwszFileInUse)
    {
	*ppwszFileInUse = NULL;
    }

    hr = S_OK;
    for (ppwsz = g_apwszDBFileMatchPatterns; NULL != *ppwsz; ppwsz++)
    {
	BOOL fFilesExist;

	hr = DoFilesExistInDir(
			    pwszDir,
			    *ppwsz,
			    &fFilesExist,
			    ppwszFileInUse);
	_JumpIfError(hr, error, "DoFilesExistInDir");

	if (fFilesExist)
	{
	    *pfFilesExist = TRUE;
	}
	if (NULL != ppwszFileInUse && NULL != *ppwszFileInUse)
	{
	    break;
	}
    }
    CSASSERT(S_OK == hr);

error:
    return(hr);
}


HRESULT
DoDBFilesExistInRegDir(
    IN WCHAR const *pwszRegName,
    OUT BOOL *pfFilesExist,
    OPTIONAL OUT WCHAR **ppwszFileInUse)
{
    HRESULT hr;
    WCHAR *pwszDir = NULL;

    *pfFilesExist = FALSE;
    if (NULL != ppwszFileInUse)
    {
	*ppwszFileInUse = NULL;
    }

    hr = myGetCertRegStrValue(NULL, NULL, NULL, pwszRegName, &pwszDir);
    if (S_OK != hr)
    {
        if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
        {
            // reg entry doesn't exist, that's fine
            goto done;
        }
        _JumpError(hr, error, "myGetCertRegStrValue");
    }

    hr = myDoDBFilesExistInDir(pwszDir, pfFilesExist, ppwszFileInUse);
    _JumpIfError(hr, error, "myDoDBFilesExistInDir");

done:
    hr = S_OK;
error:
    if (NULL != pwszDir)
    {
	LocalFree(pwszDir);
    }
    return(hr);
}


HRESULT
BuildDBFileName(
    IN WCHAR const *pwszSanitizedName,
    OUT WCHAR **ppwszDBFile)
{
    HRESULT hr;
    WCHAR *pwszDir = NULL;

    *ppwszDBFile = NULL;

    // get existing db path

    hr = myGetCertRegStrValue(NULL, NULL, NULL, wszREGDBDIRECTORY, &pwszDir);
    _JumpIfError(hr, error, "myGetCertRegStrValue");

    // form existing db file path
    hr = myBuildPathAndExt(
		    pwszDir,
		    pwszSanitizedName,
		    wszDBFILENAMEEXT,
		    ppwszDBFile);
    _JumpIfError(hr, error, "myBuildPathAndExt");

error:
    if (NULL != pwszDir)
    {
	LocalFree(pwszDir);
    }
    return(hr);
}


WCHAR const * const g_apwszDBRegNames[] =
{
    wszREGDBDIRECTORY,
    wszREGDBLOGDIRECTORY,
    wszREGDBSYSDIRECTORY,
    wszREGDBTEMPDIRECTORY,
    NULL
};


// Verify that the DB and DB Log directories in the registry contain existing
// DB files, to decide whether the DB could be reused by cert server setup.
// Also see if any of the DB files are in use -- we don't want to point to the
// same directory as the DS DB and trash the DS, for example.

HRESULT
myDoDBFilesExist(
    IN WCHAR const *pwszSanitizedName,
    OUT BOOL *pfFilesExist,
    OPTIONAL OUT WCHAR **ppwszFileInUse)
{
    HRESULT hr;
    WCHAR const * const *ppwsz;
    WCHAR *pwszDBFile = NULL;

    *pfFilesExist = FALSE;
    if (NULL != ppwszFileInUse)
    {
	*ppwszFileInUse = NULL;
    }

    // this is very primitive, just check for existence

    // get existing db file path

    hr = BuildDBFileName(pwszSanitizedName, &pwszDBFile);
    if (S_OK == hr)
    {
	// If the main DB file doesn't exist, there's no point in continuing!

	if (!myDoesFileExist(pwszDBFile))
	{
	    CSASSERT(S_OK == hr);
	    goto error;
	}
	*pfFilesExist = TRUE;

	if (NULL != ppwszFileInUse && myIsFileInUse(pwszDBFile))
	{
	    *ppwszFileInUse = pwszDBFile;
	    pwszDBFile = NULL;
	    CSASSERT(S_OK == hr);
	    goto error;
	}
    }
    else
    {
        _PrintError(hr, "BuildDBFileName");
    }

    for (ppwsz = g_apwszDBRegNames; NULL != *ppwsz; ppwsz++)
    {
	BOOL fFilesExist;

	hr = DoDBFilesExistInRegDir(*ppwsz, &fFilesExist, ppwszFileInUse);
	_JumpIfError(hr, error, "DoDBFilesExistInRegDir");

	if (fFilesExist)
	{
	    *pfFilesExist = TRUE;
	}
	if (NULL != ppwszFileInUse && NULL != *ppwszFileInUse)
	{
	    CSASSERT(S_OK == hr);
	    goto error;
	}
    }
    CSASSERT(S_OK == hr);

error:
    if (NULL != pwszDBFile)
    {
        LocalFree(pwszDBFile);
    }
    return(hr);
}


HRESULT
BackupCopyDBFile(
    IN HCSBC hcsbc,
    IN WCHAR const *pwszDBFile,
    IN WCHAR const *pwszBackupFile,
    IN DWORD dwPercentCompleteBase,
    IN DWORD dwPercentCompleteDelta,
    OUT DWORD *pdwPercentComplete)
{
    HRESULT hr;
    HRESULT hr2;
    HANDLE hFileBackup = INVALID_HANDLE_VALUE;
    BOOL fOpen = FALSE;
    LARGE_INTEGER licbFile;
    DWORD cbRead;
    DWORD cbWritten;
    DWORD dwPercentCompleteCurrent;
    DWORD ReadLoopMax;
    DWORD ReadLoopCurrent;
    DWORD cbLargeAlloc;
    BYTE *pbLargeAlloc = NULL;

    hr = myLargeAlloc(&cbLargeAlloc, &pbLargeAlloc);
    _JumpIfError(hr, error, "myLargeAlloc");

    //printf("Copy %ws to %ws\n", pwszDBFile, pwszBackupFile);

    hr = CertSrvBackupOpenFile(hcsbc, pwszDBFile, cbLargeAlloc, &licbFile);
    _JumpIfError(hr, error, "CertSrvBackupOpenFile");

    fOpen = TRUE;

    hFileBackup = CreateFile(
			pwszBackupFile,
			GENERIC_WRITE,
			0,
			NULL,
			CREATE_NEW,
			0,
			NULL);
    if (hFileBackup == INVALID_HANDLE_VALUE)
    {
	hr = myHLastError();
	_JumpErrorStr(hr, error, "CreateFile", pwszBackupFile);
    }

    dwPercentCompleteCurrent = dwPercentCompleteBase;
    ReadLoopMax =
	(DWORD) ((licbFile.QuadPart + cbLargeAlloc - 1) / cbLargeAlloc);

    //printf("BackupDBFile: Percent per Read = %u, read count = %u\n", dwPercentCompleteDelta / ReadLoopMax, ReadLoopMax);

    ReadLoopCurrent = 0;

    while (0 != licbFile.QuadPart)
    {
	hr = CertSrvBackupRead(hcsbc, pbLargeAlloc, cbLargeAlloc, &cbRead);
	_JumpIfError(hr, error, "CertSrvBackupRead");

	//printf("CertSrvBackupRead(%x)\n", cbRead);

	if (!WriteFile(hFileBackup, pbLargeAlloc, cbRead, &cbWritten, NULL))
	{
	    hr = myHLastError();
	    _JumpErrorStr(hr, error, "WriteFile", pwszBackupFile);
	}
	if (cbWritten != cbRead)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
	    _JumpErrorStr(hr, error, "WriteFile", pwszBackupFile);
	}
	licbFile.QuadPart -= cbRead;

	ReadLoopCurrent++;

	dwPercentCompleteCurrent =
		dwPercentCompleteBase +
		(ReadLoopCurrent * dwPercentCompleteDelta) / ReadLoopMax;
	CSASSERT(dwPercentCompleteCurrent <= dwPercentCompleteBase + dwPercentCompleteDelta);
	CSASSERT(*pdwPercentComplete <= dwPercentCompleteCurrent);
	*pdwPercentComplete = dwPercentCompleteCurrent;
	//printf("BackupDBFile: PercentComplete = %u\n", *pdwPercentComplete);
    }
    CSASSERT(*pdwPercentComplete <= dwPercentCompleteBase + dwPercentCompleteDelta);
    *pdwPercentComplete = dwPercentCompleteBase + dwPercentCompleteDelta;
    //printf("BackupDBFile: PercentComplete = %u (EOF)\n", *pdwPercentComplete);

error:
    if (INVALID_HANDLE_VALUE != hFileBackup)
    {
	CloseHandle(hFileBackup);
    }
    if (fOpen)
    {
	hr2 = CertSrvBackupClose(hcsbc);
	_PrintIfError(hr2, "CertSrvBackupClose");
    }
    if (NULL != pbLargeAlloc)
    {
	VirtualFree(pbLargeAlloc, 0, MEM_RELEASE);
    }
    return(hr);
}


HRESULT
BackupDBFileList(
    IN HCSBC hcsbc,
    IN BOOL fDBFiles,
    IN WCHAR const *pwszDir,
    OUT DWORD *pdwPercentComplete)
{
    HRESULT hr;
    WCHAR *pwszzList = NULL;
    WCHAR const *pwsz;
    DWORD cfile;
    DWORD cb;
    WCHAR const *pwszFile;
    WCHAR wszPath[MAX_PATH];
    DWORD dwPercentCompleteCurrent;
    DWORD dwPercentComplete1File;

    if (fDBFiles)
    {
	hr = CertSrvBackupGetDatabaseNames(hcsbc, &pwszzList, &cb);
	_JumpIfError(hr, error, "CertSrvBackupGetDatabaseNames");
    }
    else
    {
	hr = CertSrvBackupGetBackupLogs(hcsbc, &pwszzList, &cb);
	_JumpIfError(hr, error, "CertSrvBackupGetBackupLogs");
    }

    // prefix complains this might happen, then deref'd below
    if (pwszzList == NULL)
    {
        hr = E_UNEXPECTED;
        _JumpError(hr, error, "BackupDBFileList");
    }

    cfile = 0;
    for (pwsz = pwszzList; L'\0' != *pwsz; pwsz += wcslen(pwsz) + 1)
    {
	cfile++;
    }
    if (0 != cfile)
    {
	dwPercentCompleteCurrent = 0;
	dwPercentComplete1File = 100 / cfile;
	//printf("BackupDBFileList: Percent per File = %u\n", dwPercentComplete1File);
	for (pwsz = pwszzList; L'\0' != *pwsz; pwsz += wcslen(pwsz) + 1)
	{
	    pwszFile = wcsrchr(pwsz, L'\\');
	    if (NULL == pwszFile)
	    {
		pwszFile = pwsz;
	    }
	    else
	    {
		pwszFile++;
	    }
	    wcscpy(wszPath, pwszDir);
	    wcscat(wszPath, L"\\");
	    wcscat(wszPath, pwszFile);

	    DBGPRINT((
		DBG_SS_CERTLIBI,
		"BackupDBFileList: %x %ws -> %ws\n",
		*pwsz,
		&pwsz[1],
		wszPath));

	    hr = BackupCopyDBFile(
			    hcsbc,
			    &pwsz[1],
			    wszPath,
			    dwPercentCompleteCurrent,
			    dwPercentComplete1File,
			    pdwPercentComplete);
	    _JumpIfError(hr, error, "BackupCopyDBFile");

	    dwPercentCompleteCurrent += dwPercentComplete1File;
	    CSASSERT(*pdwPercentComplete == dwPercentCompleteCurrent);
	    //printf("BackupDBFileList: PercentComplete = %u\n", *pdwPercentComplete);
	}
    }
    CSASSERT(*pdwPercentComplete <= 100);
    *pdwPercentComplete = 100;
    //printf("BackupDBFileList: PercentComplete = %u (END)\n", *pdwPercentComplete);
    hr = S_OK;

error:
    if (NULL != pwszzList)
    {
	CertSrvBackupFree(pwszzList);
    }
    return(hr);
}


BOOL
myIsDirEmpty(
    IN WCHAR const *pwszDir)
{
    HANDLE hf;
    WIN32_FIND_DATA wfd;
    WCHAR wszpath[MAX_PATH];
    BOOL fEmpty = TRUE;

    wszpath[0] = L'\0';
    wcscpy(wszpath, pwszDir);
    wcscat(wszpath, L"\\*.*");

    hf = FindFirstFile(wszpath, &wfd);
    if (INVALID_HANDLE_VALUE != hf)
    {
	do {
	    if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	    {
		continue;
	    }
	    fEmpty = FALSE;
	    //printf("File: %ws\n", wfd.cFileName);
	    break;

	} while (FindNextFile(hf, &wfd));
	FindClose(hf);
    }
    return(fEmpty);
}


HRESULT
myForceDirEmpty(
    IN WCHAR const *pwszDir)
{
    HRESULT hr;
    HANDLE hf;
    WIN32_FIND_DATA wfd;
    WCHAR *pwszFile;
    WCHAR wszpath[MAX_PATH];
    wszpath[0] = L'\0';

    wcscpy(wszpath, pwszDir);
    wcscat(wszpath, L"\\*.*");
    pwszFile = &wszpath[wcslen(pwszDir)] + 1;

    hf = FindFirstFile(wszpath, &wfd);
    if (INVALID_HANDLE_VALUE == hf)
    {
	hr = myHLastError();
	_JumpIfError(hr, error, "FindFirstFile");
    }
    do {
	if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	{
	    continue;
	}
	wcscpy(pwszFile, wfd.cFileName);
	//printf("File: %ws\n", wszpath);
	DeleteFile(wszpath);

    } while (FindNextFile(hf, &wfd));
    FindClose(hf);

    hr = S_OK;

error:
    return(hr);
}


BOOL
myIsDirectory(IN WCHAR const *pwszDirectoryPath)
{
    WIN32_FILE_ATTRIBUTE_DATA data;

    return(
	GetFileAttributesEx(pwszDirectoryPath, GetFileExInfoStandard, &data) &&
	(FILE_ATTRIBUTE_DIRECTORY & data.dwFileAttributes));
}


BOOL
myIsFileInUse(
    IN WCHAR const *pwszFile)
{
    BOOL fInUse = FALSE;
    HANDLE hFile;
    
    hFile = CreateFile(
                pwszFile,
                GENERIC_WRITE, // dwDesiredAccess
                0,             // no share
                NULL,          // lpSecurityAttributes
                OPEN_EXISTING, // open only & fail if doesn't exist
                0,             // dwFlagAndAttributes
                NULL);         // hTemplateFile
    if (INVALID_HANDLE_VALUE == hFile)
    {
        if (ERROR_SHARING_VIOLATION == GetLastError())
        {
            fInUse = TRUE;
        }
    }
    else
    {
        CloseHandle(hFile);
    }
    return(fInUse);
}


HRESULT
myCreateBackupDir(
    IN WCHAR const *pwszDir,
    IN BOOL fForceOverWrite)
{
    HRESULT hr;

    if (!myIsDirectory(pwszDir))
    {
        if (!CreateDirectory(pwszDir, NULL))
        {
            hr = myHLastError();
            if (HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) != hr)
            {
                _JumpErrorStr(hr, error, "CreateDirectory", pwszDir);
            }
        } // else dir created successfully
    } // else dir already exists

    if (!myIsDirEmpty(pwszDir))
    {
	if (!fForceOverWrite)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY);
	    _JumpErrorStr(hr, error, "myIsDirEmpty", pwszDir);
	}
	hr = myForceDirEmpty(pwszDir);
	_JumpIfErrorStr(hr, error, "myForceDirEmpty", pwszDir);

	if (!myIsDirEmpty(pwszDir))
	{
	    hr = HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY);
	    _JumpErrorStr(hr, error, "myIsDirEmpty", pwszDir);
	}
    } // else is empty

    hr = S_OK;

error:
    return(hr);
}


// if Flags & CDBBACKUP_VERIFYONLY, create and verify the target directory is empty

HRESULT
myBackupDB(
    IN WCHAR const *pwszConfig,
    IN DWORD Flags,
    IN WCHAR const *pwszBackupDir,
    OPTIONAL OUT DBBACKUPPROGRESS *pdbp)
{
    HRESULT hr;
    HRESULT hr2;
    BOOL fServerOnline;
    HCSBC hcsbc;
    BOOL fBegin = FALSE;
    WCHAR *pwszPathDBDir = NULL;
    WCHAR *pwszDATFile = NULL;
    WCHAR *pwszzFileList = NULL;
    DWORD cbList;
    DBBACKUPPROGRESS dbp;
    LONG grbitJet;
    LONG BackupFlags;
    BOOL fImpersonating = FALSE;
    
    if (NULL == pwszConfig)
    {
        hr = E_INVALIDARG;
        _JumpError(hr, error, "NULL pwszConfig");
    }
    
    if (NULL == pdbp)
    {
        pdbp = &dbp;
    }
    ZeroMemory(pdbp, sizeof(*pdbp));
    
    if (!ImpersonateSelf(SecurityImpersonation))
    {
        hr = myHLastError();
        _JumpError(hr, error, "ImpersonateSelf");
    }
    fImpersonating = TRUE;
    
    hr = myEnablePrivilege(SE_BACKUP_NAME, TRUE);
    _JumpIfError(hr, error, "myEnablePrivilege");
    
    if (NULL == pwszBackupDir)
    {
        hr = E_POINTER;
        _JumpError(hr, error, "NULL parm");
    }
    if (~CDBBACKUP_BACKUPVALID & Flags)
    {
        hr = E_INVALIDARG;
        _JumpError(hr, error, "Flags");
    }
    
    if (!myIsDirectory(pwszBackupDir))
    {
        if (!CreateDirectory(pwszBackupDir, NULL))
        {
            hr = myHLastError();
            if (HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) != hr)
            {
                _JumpError(hr, error, "CreateDirectory");
            }
        }
    }
    
    hr = myBuildPathAndExt(
        pwszBackupDir,
        wszDBBACKUPSUBDIR,
        NULL,
        &pwszPathDBDir);
    _JumpIfError(hr, error, "myBuildPathAndExt");
    
    hr = myCreateBackupDir(
        pwszPathDBDir,
        (CDBBACKUP_OVERWRITE & Flags)? TRUE : FALSE);
    _JumpIfError(hr, error, "myCreateBackupDir");
    
    //if (NULL != pwszConfig)
    if (0 == (Flags & CDBBACKUP_VERIFYONLY))
    {
        hr = CertSrvIsServerOnline(pwszConfig, &fServerOnline);
        _JumpIfError(hr, error, "CertSrvIsServerOnline");
        
        //printf("Cert Server Online -> %d\n", fServerOnline);
        
        if (!fServerOnline)
        {
            hr = HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE);
            _JumpError(hr, error, "CertSrvIsServerOnline");
        }
        
        BackupFlags = CSBACKUP_TYPE_FULL;
        grbitJet = 0;
        if (CDBBACKUP_INCREMENTAL & Flags)
        {
            grbitJet |= JET_bitBackupIncremental;
            BackupFlags = CSBACKUP_TYPE_LOGS_ONLY;
        }
        if (CDBBACKUP_KEEPOLDLOGS & Flags)
        {
            // JetBeginExternalBackup can't handle setting this bit
            // grbitJet |= JET_bitKeepOldLogs;
        }
        
        hr = CertSrvBackupPrepare(pwszConfig, grbitJet, BackupFlags, &hcsbc);
        _JumpIfError(hr, error, "CertSrvBackupPrepare");
        
        fBegin = TRUE;
        
        if (0 == (CDBBACKUP_INCREMENTAL & Flags))
        {
            hr = CertSrvRestoreGetDatabaseLocations(hcsbc, &pwszzFileList, &cbList);
            _JumpIfError(hr, error, "CertSrvRestoreGetDatabaseLocations");
            
            hr = myBuildPathAndExt(
                pwszPathDBDir,
                wszDBBACKUPCERTBACKDAT,
                NULL,
                &pwszDATFile);
            _JumpIfError(hr, error, "myBuildPathAndExt");
            
            hr = EncodeToFileW(
                pwszDATFile,
                (BYTE const *) pwszzFileList,
                cbList,
                CRYPT_STRING_BINARY);
            _JumpIfError(hr, error, "EncodeToFileW");
            
            hr = BackupDBFileList(
                hcsbc,
                TRUE,
                pwszPathDBDir,
                &pdbp->dwDBPercentComplete);
            _JumpIfError(hr, error, "BackupDBFileList(DB)");
        }
        else
        {
            pdbp->dwDBPercentComplete = 100;
        }
        //printf("DB Done: dwDBPercentComplete = %u\n", pdbp->dwDBPercentComplete);
        
        hr = BackupDBFileList(
            hcsbc,
            FALSE,
            pwszPathDBDir,
            &pdbp->dwLogPercentComplete);
        _JumpIfError(hr, error, "BackupDBFileList(Log)");
        //printf("Log Done: dwLogPercentComplete = %u\n", pdbp->dwLogPercentComplete);
        
        if (0 == (CDBBACKUP_KEEPOLDLOGS & Flags))
        {
            hr = CertSrvBackupTruncateLogs(hcsbc);
            _JumpIfError(hr, error, "CertSrvBackupTruncateLogs");
        }
        pdbp->dwTruncateLogPercentComplete = 100;
        //printf("Truncate Done: dwTruncateLogPercentComplete = %u\n", pdbp->dwTruncateLogPercentComplete);
    }
    
error:
    if (NULL != pwszzFileList)
    {
        CertSrvBackupFree(pwszzFileList);
    }
    if (fBegin)
    {
        hr2 = CertSrvBackupEnd(hcsbc);
        _PrintIfError(hr2, "CertSrvBackupEnd");
        if (S_OK == hr)
        {
            hr = hr2;
        }
    }
    if (NULL != pwszDATFile)
    {
        LocalFree(pwszDATFile);
    }
    if (NULL != pwszPathDBDir)
    {
        LocalFree(pwszPathDBDir);
    }
    if (fImpersonating)
    {
        myEnablePrivilege(SE_BACKUP_NAME, FALSE);
        RevertToSelf();
    }
    return(hr);
}


// Verify the backup file names only, and return the log file numeric range.

HRESULT
myVerifyBackupDirectory(
    IN WCHAR const *pwszConfig,
    IN DWORD Flags,
    IN WCHAR const *pwszPathDBDir,
    OUT DWORD *plogMin,
    OUT DWORD *plogMax,
    OUT DWORD *pc64kDBBlocks,	// 64k blocks in DB files to be restored
    OUT DWORD *pc64kLogBlocks)	// 64k blocks in Log files to be restored
{
    HRESULT hr;
    HANDLE hf = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA wfd;
    WCHAR wszpath[2 * MAX_PATH];
    WCHAR wszfile[MAX_PATH];
    BOOL fSawEDBFile = FALSE;
    BOOL fSawDatFile = FALSE;
    DWORD cLogFiles = 0;
    WCHAR *pwszCA;
    WCHAR *pwszRevertCA = NULL;
    WCHAR *pwszSanitizedCA = NULL;
    WCHAR *pwszExt;
    WCHAR *pwsz;
    DWORD log;

    *plogMin = MAXDWORD;
    *plogMax = 0;
    *pc64kDBBlocks = 0;
    *pc64kLogBlocks = 0;
    wszpath[0] = L'\0'; 

    pwszCA = wcschr(pwszConfig, L'\\');
    if (NULL != pwszCA)
    {
	pwszCA++;	// point to CA Name

	hr = myRevertSanitizeName(pwszCA, &pwszRevertCA);
	_JumpIfError(hr, error, "myRevertSanitizeName");

	hr = mySanitizeName(pwszRevertCA, &pwszSanitizedCA);
	_JumpIfError(hr, error, "mySanitizeName");
    }

    wcscpy(wszpath, pwszPathDBDir);
    wcscat(wszpath, L"\\*.*");

    hf = FindFirstFile(wszpath, &wfd);
    if (INVALID_HANDLE_VALUE == hf)
    {
	hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
	_JumpError(hr, error, "missing backup files");
    }

    hr = HRESULT_FROM_WIN32(ERROR_DIRECTORY);
    do {
	if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	{
	    continue;
	}
	//printf("File: %ws\n", wfd.cFileName);

	wcscpy(wszfile, wfd.cFileName);

	pwszExt = wcsrchr(wszfile, L'.');
	if (NULL == pwszExt)
	{
	    _JumpError(hr, error, "file missing extension");
	}
	*pwszExt++ = L'\0';

	if (0 == lstrcmpi(&wszLOGFILENAMEEXT[1], pwszExt))
	{
	    if (0 != _wcsnicmp(wszfile, wszDBBASENAMEPARM, 3))
	    {
		_JumpErrorStr(hr, error, "bad log prefix", wfd.cFileName);
	    }
	    for (pwsz = &wszfile[3]; L'\0' != *pwsz; pwsz++)
	    {
		if (!iswxdigit(*pwsz))
		{
		    _JumpErrorStr(hr, error, "bad name digit", wfd.cFileName);
		}
	    }
	    log = wcstoul(&wszfile[3], NULL, 16);
	    if (log > *plogMax)
	    {
		//printf("Log %x: max = %x -> %x\n", log, *plogMax, log);
		*plogMax = log;
	    }
	    if (log < *plogMin)
	    {
		//printf("Log %x: min = %x -> %x\n", log, *plogMin, log);
		*plogMin = log;
	    }
	    *pc64kLogBlocks += _64kBlocks(wfd.nFileSizeHigh, wfd.nFileSizeLow);
	    cLogFiles++;
	}
	else
	if (0 == lstrcmpi(&wszDBFILENAMEEXT[1], pwszExt))
	{
	    if (fSawEDBFile)
	    {
		_JumpError(hr, error, "multiple *.edb files");
	    }
	    if (NULL != pwszSanitizedCA &&
		0 != lstrcmpi(wszfile, pwszSanitizedCA))
	    {
		_PrintErrorStr(hr, "expected base name", pwszSanitizedCA);
		_JumpErrorStr(hr, error, "base name mismatch", wfd.cFileName);
	    }
	    *pc64kDBBlocks += _64kBlocks(wfd.nFileSizeHigh, wfd.nFileSizeLow);
	    fSawEDBFile = TRUE;
	}
	else
	if (0 == lstrcmpi(&wszDATFILENAMEEXT[1], pwszExt))
	{
	    if (fSawDatFile)
	    {
		_JumpError(hr, error, "multiple *.dat files");
	    }
	    if (lstrcmpi(wfd.cFileName, wszDBBACKUPCERTBACKDAT))
	    {
		_JumpErrorStr(hr, error, "unexpected file", wfd.cFileName);
	    }
	    fSawDatFile = TRUE;
	}
	else
	{
	    _JumpErrorStr(hr, error, "unexpected extension", wfd.cFileName);
	}
    } while (FindNextFile(hf, &wfd));

    //printf("clog=%u: %u - %u  edb=%u\n", cLogFiles, *plogMin, *plogMax, fSawEDBFile);

    if (0 == cLogFiles)
    {
	_JumpError(hr, error, "missing log file(s)");
    }
    if (0 == (CDBBACKUP_INCREMENTAL & Flags))
    {
	if (!fSawEDBFile || !fSawDatFile)
	{
	    _JumpError(hr, error, "missing full backup file(s)");
	}
    }
    else
    {
	if (fSawEDBFile || fSawDatFile)
	{
	    _JumpError(hr, error, "unexpected incremental backup file(s)");
	}
    }

    if (*plogMax - *plogMin + 1 != cLogFiles)
    {
	_JumpError(hr, error, "missing log file(s)");
    }
    hr = S_OK;

error:
    if (NULL != pwszRevertCA)
    {
	LocalFree(pwszRevertCA);
    }
    if (NULL != pwszSanitizedCA)
    {
	LocalFree(pwszSanitizedCA);
    }
    if (INVALID_HANDLE_VALUE != hf)
    {
	FindClose(hf);
    }
    return(hr);
}


HRESULT
myGetRegUNCDBDir(
    IN HKEY hkey,
    IN WCHAR const *pwszReg,
    OPTIONAL IN WCHAR const *pwszServer,
    IN WCHAR const **ppwszUNCDir)
{
    HRESULT hr;
    DWORD dwType;
    DWORD cb;
    WCHAR *pwszDir = NULL;
    WCHAR *pwszUNCDir;

    *ppwszUNCDir = NULL;
    hr = RegQueryValueEx(hkey, pwszReg, NULL, &dwType, NULL, &cb);
    if (S_OK != hr)
    {
	hr = myHError(hr);
	_JumpErrorStr(hr, error, "RegQueryValueEx", pwszReg);
    }

    pwszDir = (WCHAR *) LocalAlloc(LMEM_FIXED, cb);
    if (NULL == pwszDir)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    hr = RegQueryValueEx(hkey, pwszReg, NULL, &dwType, (BYTE *) pwszDir, &cb);
    if (S_OK != hr)
    {
	hr = myHError(hr);
	_JumpErrorStr(hr, error, "RegQueryValueEx", pwszReg);
    }

    hr = myConvertLocalPathToUNC(pwszServer, pwszDir, &pwszUNCDir);
    _JumpIfError(hr, error, "myConvertLocalPathToUNC");

    *ppwszUNCDir = pwszUNCDir;

error:
    if (NULL != pwszDir)
    {
	LocalFree(pwszDir);
    }
    return(hr);
}


HRESULT
myCopyUNCPath(
    IN WCHAR const *pwszIn,
    OPTIONAL IN WCHAR const *pwszDnsName,
    OUT WCHAR const **ppwszOut)
{
    HRESULT hr;
    WCHAR *pwszOut;
    WCHAR const *pwsz;
    
    *ppwszOut = NULL;

    if (L'\\' != pwszIn[0] || L'\\' != pwszIn[1])
    {
	hr = E_INVALIDARG;
	_JumpError(hr, error, "bad parm");
    }
    if (NULL == pwszDnsName)
    {
	hr = myConvertUNCPathToLocal(pwszIn, &pwszOut);
	_JumpIfError(hr, error, "myConvertUNCPathToLocal");
    }
    else
    {
	pwsz = wcschr(&pwszIn[2], L'\\');
	if (NULL == pwsz)
	{
	    hr = E_INVALIDARG;
	    _JumpError(hr, error, "bad parm");
	}
	pwszOut = (WCHAR *) LocalAlloc(
		LMEM_FIXED,
		(2 + wcslen(pwszDnsName) + wcslen(pwsz) + 1) * sizeof(WCHAR));
	if (NULL == pwszOut)
	{
	    hr = E_OUTOFMEMORY;
	    _JumpError(hr, error, "LocalAlloc");
	}
	wcscpy(pwszOut, L"\\\\");
	wcscat(pwszOut, pwszDnsName);
	wcscat(pwszOut, pwsz);
    }
    *ppwszOut = pwszOut;
    hr = S_OK;

error:
    return(hr);
}


HRESULT
myGetDBPaths(
    IN WCHAR const *pwszConfig,
    OPTIONAL IN WCHAR const *pwszLogPath,
    OPTIONAL IN WCHAR const *pwszzFileList,
    OUT WCHAR const **ppwszDBDir,
    OUT WCHAR const **ppwszLogDir,
    OUT WCHAR const **ppwszSystemDir)
{
    HRESULT hr;
    HKEY hkey = NULL;
    WCHAR *pwszDnsName = NULL;
    WCHAR *pwszRegPath = NULL;
    WCHAR *pwszDBDir = NULL;
    WCHAR const *pwsz;
    WCHAR const *pwszT;
    BOOL fLocal;

    *ppwszDBDir = NULL;
    *ppwszLogDir = NULL;
    *ppwszSystemDir = NULL;

    hr = myIsConfigLocal(pwszConfig, NULL, &fLocal);
    _JumpIfError(hr, error, "myIsConfigLocal");

    if (fLocal)
    {
	pwszConfig = NULL;
    }
    else
    {
	hr = myGetMachineDnsName(&pwszDnsName);
	_JumpIfError(hr, error, "myGetMachineDnsName");
    }

    hr = myRegOpenRelativeKey(
			fLocal? NULL : pwszConfig,
			L"",
			0,
			&pwszRegPath,
			NULL,		// ppwszName
			&hkey);
    _JumpIfErrorStr(hr, error, "myRegOpenRelativeKey", pwszConfig);

    // Find old database path:

    pwszT = NULL;
    if (NULL != pwszzFileList)
    {
	for (pwsz = pwszzFileList; L'\0' != *pwsz; pwsz += wcslen(pwsz) + 1)
	{
	    if (CSBFT_CERTSERVER_DATABASE == *pwsz)
	    {
		pwsz++;
		pwszT = wcsrchr(pwsz, L'\\');
		break;
	    }
	}
    }

    if (NULL != pwszT)
    {
	DWORD cwc = SAFE_SUBTRACT_POINTERS(pwszT, pwsz);

	pwszDBDir = (WCHAR *) LocalAlloc(LMEM_FIXED, (cwc + 1) * sizeof(WCHAR));
	if (NULL == pwszDBDir)
	{
	    hr = E_OUTOFMEMORY;
	    _JumpError(hr, error, "LocalAlloc");
	}
	CopyMemory(pwszDBDir, pwsz, cwc * sizeof(WCHAR));
	pwszDBDir[cwc] = L'\0';

	hr = myCopyUNCPath(pwszDBDir, pwszDnsName, ppwszDBDir);
	_JumpIfError(hr, error, "myCopyUNCPath");
    }
    else
    {
	hr = myGetRegUNCDBDir(hkey, wszREGDBDIRECTORY, pwszDnsName, ppwszDBDir);
	_JumpIfError(hr, error, "myGetRegUNCDBDir");
    }

    if (NULL != pwszLogPath)
    {
	hr = myCopyUNCPath(pwszLogPath, pwszDnsName, ppwszLogDir);
	_JumpIfError(hr, error, "myCopyUNCPath");
    }
    else
    {
	hr = myGetRegUNCDBDir(
			hkey,
			wszREGDBLOGDIRECTORY,
			pwszDnsName,
			ppwszLogDir);
	_JumpIfError(hr, error, "myGetRegUNCDBDir");
    }

    hr = myGetRegUNCDBDir(
			hkey,
			wszREGDBSYSDIRECTORY,
			pwszDnsName,
			ppwszSystemDir);
    _JumpIfError(hr, error, "myGetRegUNCDBDir");

error:
    if (S_OK != hr)
    {
	if (NULL != *ppwszDBDir)
	{
	    LocalFree(const_cast<WCHAR *>(*ppwszDBDir));
	    *ppwszDBDir = NULL;
	}
	if (NULL != *ppwszLogDir)
	{
	    LocalFree(const_cast<WCHAR *>(*ppwszLogDir));
	    *ppwszLogDir = NULL;
	}
	if (NULL != *ppwszSystemDir)
	{
	    LocalFree(const_cast<WCHAR *>(*ppwszSystemDir));
	    *ppwszSystemDir = NULL;
	}
    }
    if (NULL != hkey)
    {
        RegCloseKey(hkey);
    }
    if (NULL != pwszDBDir)
    {
	LocalFree(pwszDBDir);
    }
    if (NULL != pwszRegPath)
    {
	LocalFree(pwszRegPath);
    }
    if (NULL != pwszDnsName)
    {
	LocalFree(pwszDnsName);
    }
    return(hr);
}


HRESULT
RestoreCopyFile(
    IN BOOL fForceOverWrite,
    IN WCHAR const *pwszSourceDir,
    IN WCHAR const *pwszTargetDir,
    IN WCHAR const *pwszFile,
    IN DWORD nFileSizeHigh,
    IN DWORD nFileSizeLow,
    IN DWORD c64kBlocksTotal,		// total file size
    IN OUT DWORD *pc64kBlocksCurrent,	// current file size sum
    IN OUT DWORD *pdwPercentComplete)
{
    HRESULT hr;
    WCHAR *pwszSource = NULL;
    WCHAR *pwszTarget = NULL;
    HANDLE hTarget = INVALID_HANDLE_VALUE;
    HANDLE hSource = INVALID_HANDLE_VALUE;
    DWORD dwPercentCompleteCurrent;
    LARGE_INTEGER licb;
    LARGE_INTEGER licbRead;
    DWORD cbRead;
    DWORD cbWritten;
    DWORD cbLargeAlloc;
    BYTE *pbLargeAlloc = NULL;
    DWORD c64kBlocksFile;
    DWORD dwPercentComplete;

    licb.HighPart = nFileSizeHigh;
    licb.LowPart = nFileSizeLow;

    hr = myBuildPathAndExt(pwszSourceDir, pwszFile, NULL, &pwszSource);
    _JumpIfError(hr, error, "myBuildPathAndExt");

    hr = myBuildPathAndExt(pwszTargetDir, pwszFile, NULL, &pwszTarget);
    _JumpIfError(hr, error, "myBuildPathAndExt");

    hr = myLargeAlloc(&cbLargeAlloc, &pbLargeAlloc);
    _JumpIfError(hr, error, "myLargeAlloc");

    hSource = CreateFile(
			pwszSource,
			GENERIC_READ,
			0,
			NULL,
			OPEN_EXISTING,
			0,
			NULL);
    if (hSource == INVALID_HANDLE_VALUE)
    {
	hr = myHLastError();
	_JumpErrorStr(hr, error, "CreateFile", pwszSource);
    }
    hTarget = CreateFile(
			pwszTarget,
			GENERIC_WRITE,
			0,
			NULL,
			fForceOverWrite? CREATE_ALWAYS : CREATE_NEW,
			0,
			NULL);
    if (hTarget == INVALID_HANDLE_VALUE)
    {
	hr = myHLastError();
	_JumpErrorStr(hr, error, "CreateFile", pwszTarget);
    }

    licbRead.QuadPart = 0;
    c64kBlocksFile = 0;
    while (licbRead.QuadPart < licb.QuadPart)
    {
	if (!ReadFile(hSource, pbLargeAlloc, cbLargeAlloc, &cbRead, NULL))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "ReadFile");
	}
	//printf("ReadFile(%x)\n", cbRead);

	if (!WriteFile(hTarget, pbLargeAlloc, cbRead, &cbWritten, NULL))
	{
	    hr = myHLastError();
	    _JumpErrorStr(hr, error, "WriteFile", pwszTarget);
	}
	if (cbWritten != cbRead)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
	    _JumpErrorStr(hr, error, "WriteFile", pwszTarget);
	}
	licbRead.QuadPart += cbRead;

	c64kBlocksFile = _64kBlocks(licbRead.HighPart, licbRead.LowPart);
	dwPercentComplete =
	    (100 * (c64kBlocksFile + *pc64kBlocksCurrent)) / c64kBlocksTotal;

	CSASSERT(*pdwPercentComplete <= dwPercentComplete);
	*pdwPercentComplete = dwPercentComplete;
	//printf("RestoreCopyFile0: PercentComplete = %u\n", *pdwPercentComplete);
    }
    *pc64kBlocksCurrent += c64kBlocksFile;
    dwPercentComplete = (100 * *pc64kBlocksCurrent) / c64kBlocksTotal;
    CSASSERT(*pdwPercentComplete <= dwPercentComplete);
    *pdwPercentComplete = dwPercentComplete;
    //printf("RestoreCopyFile1: PercentComplete = %u\n", *pdwPercentComplete);
    hr = S_OK;

error:
    if (INVALID_HANDLE_VALUE != hTarget)
    {
	CloseHandle(hTarget);
    }
    if (INVALID_HANDLE_VALUE != hSource)
    {
	CloseHandle(hSource);
    }
    if (NULL != pwszSource)
    {
	LocalFree(pwszSource);
    }
    if (NULL != pwszTarget)
    {
	LocalFree(pwszTarget);
    }
    if (NULL != pbLargeAlloc)
    {
	VirtualFree(pbLargeAlloc, 0, MEM_RELEASE);
    }
    return(hr);
}


HRESULT
RestoreCopyFilePattern(
    IN BOOL fForceOverWrite,
    IN WCHAR const *pwszSourceDir,
    IN WCHAR const *pwszTargetDir,
    IN WCHAR const *pwszFilePattern,
    IN DWORD c64kBlocksTotal,		// total file size
    IN OUT DWORD *pc64kBlocksCurrent,	// current file size sum
    IN OUT DWORD *pdwPercentComplete)
{
    HRESULT hr;
    WCHAR *pwszPattern = NULL;
    HANDLE hf = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA wfd;
    
    hr = myBuildPathAndExt(pwszSourceDir, pwszFilePattern, NULL, &pwszPattern);
    _JumpIfError(hr, error, "myBuildPathAndExt");

    hf = FindFirstFile(pwszPattern, &wfd);
    if (INVALID_HANDLE_VALUE == hf)
    {
	hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
	_JumpErrorStr(hr, error, "missing source files", pwszPattern);
    }

    hr = HRESULT_FROM_WIN32(ERROR_DIRECTORY);
    do {
	if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	{
	    continue;
	}
	//printf("File: %ws\n", wfd.cFileName);
	hr = RestoreCopyFile(
			fForceOverWrite,
			pwszSourceDir,		// source dir
			pwszTargetDir,		// target dir
			wfd.cFileName,
			wfd.nFileSizeHigh,
			wfd.nFileSizeLow,
			c64kBlocksTotal,	// total file size
			pc64kBlocksCurrent,	// current file size sum
			pdwPercentComplete);
	_JumpIfError(hr, error, "RestoreCopyFile");

    } while (FindNextFile(hf, &wfd));
    hr = S_OK;

error:
    if (INVALID_HANDLE_VALUE != hf)
    {
	FindClose(hf);
    }
    if (NULL != pwszPattern)
    {
	LocalFree(pwszPattern);
    }
    return(hr);
}


HRESULT
myRestoreDBFiles(
    IN WCHAR const *pwszConfig,
    IN DWORD Flags,
    IN WCHAR const *pwszBackupDir,
    OPTIONAL IN WCHAR const *pwszLogPath,
    OPTIONAL IN WCHAR const *pwszzFileList,	// NULL if incremental restore
    IN DWORD c64kDBBlocks,
    IN DWORD c64kLogBlocks,
    OPTIONAL OUT DBBACKUPPROGRESS *pdbp)
{
    HRESULT hr;
    DWORD i;
#define IDIR_DB		0
#define IDIR_LOG	1
#define IDIR_SYSTEM	2
    WCHAR const *apwszDirs[3] = { NULL, NULL, NULL };
    DWORD c64kBlocksCurrent;
    BOOL fForceOverWrite = 0 != (CDBBACKUP_OVERWRITE & Flags);
    WCHAR *pwszFileInUse = NULL;

    // Get DB, Log & System paths from registry

    hr = myGetDBPaths(
		    pwszConfig,
		    pwszLogPath,
		    pwszzFileList,
		    &apwszDirs[IDIR_DB],
		    &apwszDirs[IDIR_LOG],
		    &apwszDirs[IDIR_SYSTEM]);
    _JumpIfError(hr, error, "myGetDBPaths");

    DBGPRINT((DBG_SS_CERTLIBI, "DBDir:  %ws\n", apwszDirs[IDIR_DB]));
    DBGPRINT((DBG_SS_CERTLIBI, "LogDir: %ws\n", apwszDirs[IDIR_LOG]));
    DBGPRINT((DBG_SS_CERTLIBI, "SysDir: %ws\n", apwszDirs[IDIR_SYSTEM]));

    CSASSERT((NULL == pwszzFileList) ^ (0 == (CDBBACKUP_INCREMENTAL & Flags)));
    for (i = 0; i < ARRAYSIZE(apwszDirs); i++)
    {
	BOOL fFilesExist;

	if (!myIsDirectory(apwszDirs[i]))
	{
	    hr = HRESULT_FROM_WIN32(ERROR_DIRECTORY);
	    _JumpErrorStr(hr, error, "not a directory", apwszDirs[i]);
	}
	hr = myDoDBFilesExistInDir(apwszDirs[i], &fFilesExist, &pwszFileInUse);
	_JumpIfError(hr, error, "myDoDBFilesExistInDir");

	if (NULL != pwszFileInUse)
	{
	    _PrintErrorStr(
		    HRESULT_FROM_WIN32(ERROR_BUSY),
		    "myDoDBFilesExistInDir",
		    pwszFileInUse);
	}
	if (!fFilesExist)
	{
	    if (CDBBACKUP_INCREMENTAL & Flags)
	    {
		// Incremental restore -- some DB files should already exist

		hr = HRESULT_FROM_WIN32(ERROR_DIRECTORY);
		_JumpErrorStr(hr, error, "myDoDBFilesExistInDir", apwszDirs[i]);
	    }
	}
	else if (0 == (CDBBACKUP_INCREMENTAL & Flags))
	{
	    // Full restore -- no DB files should exist yet

	    if (!fForceOverWrite)
	    {
		hr = HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY);
		_JumpErrorStr(
			hr,
			error,
			"myDoDBFilesExistInDir",
			NULL != pwszFileInUse? pwszFileInUse : apwszDirs[i]);
	    }
	    hr = myDeleteDBFilesInDir(apwszDirs[i]);
	    if (S_OK != hr)
	    {
		_PrintErrorStr(hr, "myDeleteDBFilesInDir", apwszDirs[i]);
	    }
	}
    }

    // copy files to appropriate target directories

    if (0 == (CDBBACKUP_INCREMENTAL & Flags))
    {
	c64kBlocksCurrent = 0;
	hr = RestoreCopyFilePattern(
			fForceOverWrite,
			pwszBackupDir,		// source dir
			apwszDirs[IDIR_DB],	// target dir
			L"*" wszDBFILENAMEEXT,	// match pattern
			c64kDBBlocks,
			&c64kBlocksCurrent,	// current total file size
			&pdbp->dwDBPercentComplete);
	_JumpIfError(hr, error, "RestoreCopyFile");

	CSASSERT(c64kDBBlocks == c64kBlocksCurrent);
    }
    CSASSERT(100 >= pdbp->dwDBPercentComplete);
    pdbp->dwDBPercentComplete = 100;

    c64kBlocksCurrent = 0;
    hr = RestoreCopyFilePattern(
		    fForceOverWrite,
		    pwszBackupDir,		// source dir
		    apwszDirs[IDIR_LOG],	// target dir
		    L"*" wszLOGFILENAMEEXT,	// match pattern
		    c64kLogBlocks,
		    &c64kBlocksCurrent,		// current total file size
		    &pdbp->dwLogPercentComplete);
    _JumpIfError(hr, error, "RestoreCopyFile");

    CSASSERT(c64kLogBlocks == c64kBlocksCurrent);

    CSASSERT(100 >= pdbp->dwLogPercentComplete);
    pdbp->dwLogPercentComplete = 100;

    CSASSERT(100 >= pdbp->dwTruncateLogPercentComplete);
    pdbp->dwTruncateLogPercentComplete = 100;

    hr = S_OK;

error:
    if (NULL != pwszFileInUse)
    {
	LocalFree(pwszFileInUse);
    }
    for (i = 0; i < ARRAYSIZE(apwszDirs); i++)
    {
	if (NULL != apwszDirs[i])
	{
	    LocalFree(const_cast<WCHAR *>(apwszDirs[i]));
	}
    }
    return(hr);
}


HRESULT
myDeleteRestoreInProgressKey(
    IN WCHAR const *pwszConfig)
{
    HRESULT hr;
    HKEY hkey = NULL;
    WCHAR *pwszRegPath = NULL;

    hr = myRegOpenRelativeKey(
			pwszConfig,
			L"",
			RORKF_CREATESUBKEYS,
			&pwszRegPath,
			NULL,		// ppwszName
			&hkey);
    _JumpIfErrorStr(hr, error, "myRegOpenRelativeKey", pwszConfig);

    hr = RegDeleteKey(hkey, wszREGKEYRESTOREINPROGRESS);
    _JumpIfError(hr, error, "RegDeleteKey");

error:
    if (NULL != hkey)
    {
	RegCloseKey(hkey);
    }
    if (NULL != pwszRegPath)
    {
	LocalFree(pwszRegPath);
    }
    return(hr);
}


// If CDBBACKUP_VERIFYONLY, only verify the passed directory contains valid
// files.  If pwszBackupDir is NULL, delete the RestoreInProgress registry key.

HRESULT
myRestoreDB(
    IN WCHAR const *pwszConfig,
    IN DWORD Flags,
    OPTIONAL IN WCHAR const *pwszBackupDir,
    OPTIONAL IN WCHAR const *pwszCheckPointFilePath,
    OPTIONAL IN WCHAR const *pwszLogPath,
    OPTIONAL IN WCHAR const *pwszBackupLogPath,
    OPTIONAL OUT DBBACKUPPROGRESS *pdbp)
{
    HRESULT hr;
    HRESULT hr2;
    WCHAR buf[MAX_PATH];
    WCHAR *pwszPathDBDir = NULL;
    WCHAR *pwszDATFile = NULL;
    WCHAR *pwszzFileList = NULL;
    DWORD cbList;
    CSEDB_RSTMAP RstMap[1];
    DWORD crstmap = 0;
    WCHAR *pwszFile;
    DWORD logMin;
    DWORD logMax;
    HCSBC hcsbc;
    BOOL fBegin = FALSE;
    BOOL fImpersonating = FALSE;
    DBBACKUPPROGRESS dbp;
    DWORD c64kDBBlocks;		// 64k blocks in DB files to be restored
    DWORD c64kLogBlocks;	// 64k blocks in Log files to be restored

    if (NULL == pdbp)
    {
	pdbp = &dbp;
    }
    ZeroMemory(pdbp, sizeof(*pdbp));

    if (!ImpersonateSelf(SecurityImpersonation))
    {
        hr = myHLastError();
        _JumpError(hr, error, "ImpersonateSelf");
    }
    fImpersonating = TRUE;

    hr = myEnablePrivilege(SE_RESTORE_NAME, TRUE);
    _JumpIfError(hr, error, "myEnablePrivilege");
    hr = myEnablePrivilege(SE_BACKUP_NAME, TRUE);
    _JumpIfError(hr, error, "myEnablePrivilege");

    if (NULL == pwszConfig ||
	((CDBBACKUP_VERIFYONLY & Flags) && NULL == pwszBackupDir))
    {
	hr = E_POINTER;
	_JumpError(hr, error, "NULL parm");
    }
    if (NULL != pwszBackupDir)
    {
	if (!GetFullPathName(pwszBackupDir, ARRAYSIZE(buf), buf, &pwszFile))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "GetFullPathName");
	}
	hr = myBuildPathAndExt(buf, wszDBBACKUPSUBDIR, NULL, &pwszPathDBDir);
	_JumpIfError(hr, error, "myBuildPathAndExt");

	hr = myVerifyBackupDirectory(
				pwszConfig,
				Flags,
				pwszPathDBDir,
				&logMin,
				&logMax,
				&c64kDBBlocks,
				&c64kLogBlocks);
	_JumpIfError(hr, error, "myVerifyBackupDirectory");

	DBGPRINT((
		DBG_SS_CERTLIBI,
		"c64kBlocks=%u+%u\n",
		c64kDBBlocks,
		c64kLogBlocks));

	if (0 == (CDBBACKUP_INCREMENTAL & Flags))
	{
	    hr = myBuildPathAndExt(
			    pwszPathDBDir,
			    wszDBBACKUPCERTBACKDAT,
			    NULL,
			    &pwszDATFile);
	    _JumpIfError(hr, error, "myBuildPathAndExt");

	    hr = DecodeFileW(
			pwszDATFile,
			(BYTE **) &pwszzFileList,
			&cbList,
			CRYPT_STRING_BINARY);
	    _JumpIfError(hr, error, "DecodeFileW");

	    if (2 * sizeof(WCHAR) >= cbList ||
		L'\0' != pwszzFileList[cbList/sizeof(WCHAR) - 1] ||
		L'\0' != pwszzFileList[cbList/sizeof(WCHAR) - 2])
	    {
		hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
		_JumpError(hr, error, "pwszzFileList malformed");
	    }
	    RstMap[0].pwszDatabaseName = pwszzFileList;
	    RstMap[0].pwszNewDatabaseName = pwszzFileList;
	    crstmap = 1;
	}
	if (0 == (CDBBACKUP_VERIFYONLY & Flags))
	{
	    hr = myRestoreDBFiles(
			    pwszConfig,
			    Flags,
			    pwszPathDBDir,
			    pwszLogPath,
			    pwszzFileList,
			    c64kDBBlocks,
			    c64kLogBlocks,
			    pdbp);
	    _JumpIfError(hr, error, "myRestoreDBFiles");

	    hr = CertSrvRestorePrepare(pwszConfig, CSRESTORE_TYPE_FULL, &hcsbc);
	    _JumpIfError(hr, error, "CertSrvRestorePrepare");

	    fBegin = TRUE;

	    hr = CertSrvRestoreRegister(
			    hcsbc,
			    pwszCheckPointFilePath,
			    pwszLogPath,
			    0 == crstmap? NULL : RstMap,
			    crstmap,
			    pwszBackupLogPath,
			    logMin,
			    logMax);

	    // When running only as backup operator, we don't have rights
	    // in the registry and CertSrvRestoreRegister fails with access
	    // denied. We try to mark for restore through a file.

	    if (E_ACCESSDENIED == hr)
	    {
		hr = CertSrvRestoreRegisterThroughFile(
				hcsbc,
				pwszCheckPointFilePath,
				pwszLogPath,
				0 == crstmap? NULL : RstMap,
				crstmap,
				pwszBackupLogPath,
				logMin,
				logMax);
		_JumpIfError(hr, error, "CertSrvRestoreRegisterThroughFile");
	    }
	    else
	    {
		_JumpIfError(hr, error, "CertSrvRestoreRegister");

		hr = CertSrvRestoreRegisterComplete(hcsbc, S_OK);
		_JumpIfError(hr, error, "CertSrvRestoreRegisterComplete");
	    }
	}
    }
    else if (0 == (CDBBACKUP_VERIFYONLY & Flags))
    {
	hr = myDeleteRestoreInProgressKey(pwszConfig);
	_JumpIfError(hr, error, "myDeleteRestoreInProgressKey");
    }
    hr = S_OK;

error:
    if (fBegin)
    {
	hr2 = CertSrvRestoreEnd(hcsbc);
	_PrintIfError(hr2, "CertSrvBackupEnd");
	if (S_OK == hr)
	{
	    hr = hr2;
	}
    }
    if (NULL != pwszzFileList)
    {
	LocalFree(pwszzFileList);
    }
    if (NULL != pwszDATFile)
    {
	LocalFree(pwszDATFile);
    }
    if (NULL != pwszPathDBDir)
    {
	LocalFree(pwszPathDBDir);
    }
    if (fImpersonating)
    {
        myEnablePrivilege(SE_BACKUP_NAME, FALSE);
        myEnablePrivilege(SE_RESTORE_NAME, FALSE);
        RevertToSelf();
    }
    return(hr);
}


HRESULT
myPFXExportCertStore(
    IN HCERTSTORE hStore,
    OUT CRYPT_DATA_BLOB *ppfx,
    IN WCHAR const *pwszPassword,
    IN DWORD dwFlags)
{
    HRESULT hr;

    ppfx->pbData = NULL;
    if (!PFXExportCertStore(hStore, ppfx, pwszPassword, dwFlags))
    {
        hr = myHLastError();
        _JumpError(hr, error, "PFXExportCertStore");
    }
    ppfx->pbData = (BYTE *) LocalAlloc(LMEM_FIXED, ppfx->cbData);
    if (NULL == ppfx->pbData)
    {
        hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "no memory for PFX blob");
    }

    if (!PFXExportCertStore(hStore, ppfx, pwszPassword, dwFlags))
    {
        hr = myHLastError();
        _JumpError(hr, error, "PFXExportCertStore");
    }
    hr = S_OK;

error:
    return(hr);
}


////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////

HRESULT
myAddChainToMemoryStore(
    IN HCERTSTORE hMemoryStore,
    IN CERT_CONTEXT const *pCertContext)
{
    HRESULT hr;
    DWORD i;
    CERT_CHAIN_CONTEXT const *pCertChainContext = NULL;
    CERT_CHAIN_PARA CertChainPara;
    CERT_SIMPLE_CHAIN *pSimpleChain;

    ZeroMemory(&CertChainPara, sizeof(CertChainPara));
    CertChainPara.cbSize = sizeof(CertChainPara);

    if (!CertGetCertificateChain(
			    HCCE_LOCAL_MACHINE,
			    pCertContext,
			    NULL,
			    NULL,
			    &CertChainPara,
			    0,
			    NULL,
			    &pCertChainContext))
    {
        hr = myHLastError();
        _JumpError(hr, error, "CertGetCertificateChain");
    }

    // make sure there is at least 1 simple chain

    if (0 == pCertChainContext->cChain)
    {
	hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
        _JumpError(hr, error, "pCertChainContext->cChain");
    }

    pSimpleChain = pCertChainContext->rgpChain[0];
    for (i = 0; i < pSimpleChain->cElement; i++)
    {
	if (!CertAddCertificateContextToStore(
			    hMemoryStore,
			    pSimpleChain->rgpElement[i]->pCertContext,
			    CERT_STORE_ADD_REPLACE_EXISTING,
			    NULL))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CertAddCertificateContextToStore");
	}
    }
    hr = S_OK;

error:
    if (pCertChainContext != NULL)
    {
	CertFreeCertificateChain(pCertChainContext);
    }
    return(hr);
}


HRESULT
SaveCACertChainToMemoryStore(
    IN WCHAR const *pwszSanitizedName,
    IN DWORD iCert,
    IN HCERTSTORE hMyStore,
    IN HCERTSTORE hTempMemoryStore)
{
    HRESULT hr;
    CERT_CONTEXT const *pccCA = NULL;
    CRYPT_KEY_PROV_INFO *pkpi = NULL;
    DWORD NameId;

    hr = myFindCACertByHashIndex(
			    hMyStore,
			    pwszSanitizedName,
			    CSRH_CASIGCERT,
			    iCert,
			    &NameId,
			    &pccCA);
    _JumpIfError(hr, error, "myFindCACertByHashIndex");

    hr = myRepairCertKeyProviderInfo(pccCA, TRUE, &pkpi);
    if (S_OK != hr)
    {
        if (CRYPT_E_NOT_FOUND != hr)
        {
            _JumpError(hr, error, "myRepairCertKeyProviderInfo");
        }
    }
    else if (NULL != pkpi)
    {
	BOOL fMatchingKey;

	hr = myVerifyPublicKey(
			pccCA,
			FALSE,
			NULL,		// pKeyProvInfo
			NULL,		// pPublicKeyInfo
			&fMatchingKey);
        if (S_OK != hr)
        {
            if (!IsHrSkipPrivateKey(hr))
            {
                _JumpError(hr, error, "myVerifyPublicKey");
            }
            _PrintError2(hr, "myVerifyPublicKey", hr);
        }
	else if (!fMatchingKey)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "Key doesn't match cert");
	}
    }

    // Begin Chain Building

    hr = myAddChainToMemoryStore(hTempMemoryStore, pccCA);
    _JumpIfError(hr, error, "myAddChainToMemoryStore");

    // End Chain Building

error:
    if (NULL != pkpi)
    {
	LocalFree(pkpi);
    }
    if (NULL != pccCA)
    {
	CertFreeCertificateContext(pccCA);
    }
    return(hr);
}


HRESULT
myCertServerExportPFX(
    IN WCHAR const *pwszCA,
    IN WCHAR const *pwszBackupDir,
    IN WCHAR const *pwszPassword,
    IN BOOL fForceOverWrite,
    IN BOOL fMustExportPrivateKeys,
    OPTIONAL OUT WCHAR **ppwszPFXFile)
{
    HRESULT hr;
    HCERTSTORE hMyStore = NULL;
    HCERTSTORE hTempMemoryStore = NULL;
    CRYPT_DATA_BLOB pfx;
    WCHAR *pwszPFXFile = NULL;
    BOOL fImpersonating = FALSE;
    WCHAR *pwszSanitizedCA = NULL;
    WCHAR *pwszRevertCA = NULL;
    DWORD cCACert;
    DWORD cCACertSaved;
    DWORD i;

    pfx.pbData = NULL;

    if (!ImpersonateSelf(SecurityImpersonation))
    {
        hr = myHLastError();
        _JumpError(hr, error, "ImpersonateSelf");
    }
    fImpersonating = TRUE;

    hr = myEnablePrivilege(SE_BACKUP_NAME, TRUE);
    _JumpIfError(hr, error, "myEnablePrivilege");

    if (NULL != ppwszPFXFile)
    {
	*ppwszPFXFile = NULL;
    }

    while (TRUE)
    {
	hr = mySanitizeName(pwszCA, &pwszSanitizedCA);
	_JumpIfError(hr, error, "mySanitizeName");

	// get CA cert count
	hr = myGetCARegHashCount(pwszSanitizedCA, CSRH_CASIGCERT, &cCACert);
	if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr &&
	    NULL == pwszRevertCA)
	{
	    LocalFree(pwszSanitizedCA);
	    pwszSanitizedCA = NULL;

	    hr = myRevertSanitizeName(pwszCA, &pwszRevertCA);
	    _JumpIfError(hr, error, "myRevertSanitizeName");

	    pwszCA = pwszRevertCA;
	    continue;
	}
	_JumpIfError(hr, error, "myGetCARegHashCount");

	if (NULL != pwszRevertCA)
	{
	    DBGPRINT((
		DBG_SS_CERTLIB,
		"myCertServerExportPFX called with Sanitized Name: %ws\n",
		pwszSanitizedCA));
	}
	break;
    }

    if (!myIsDirectory(pwszBackupDir))
    {
        if (!CreateDirectory(pwszBackupDir, NULL))
        {
	    hr = myHLastError();
	    if (HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) != hr)
	    {
		_JumpError(hr, error, "CreateDirectory");
	    }
        }
    }

    pwszPFXFile = (WCHAR *) LocalAlloc(
				    LMEM_FIXED,
				    (wcslen(pwszBackupDir) +
				     1 +
				     wcslen(pwszSanitizedCA) +
				     ARRAYSIZE(wszPFXFILENAMEEXT)) *
					 sizeof(WCHAR));
    if (NULL == pwszPFXFile)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    wcscpy(pwszPFXFile, pwszBackupDir);
    wcscat(pwszPFXFile, L"\\");
    wcscat(pwszPFXFile, pwszSanitizedCA);
    wcscat(pwszPFXFile, wszPFXFILENAMEEXT);

    DBGPRINT((DBG_SS_CERTLIBI, "myCertServerExportPFX(%ws)\n", pwszPFXFile));

    hMyStore = CertOpenStore(
			CERT_STORE_PROV_SYSTEM_W,
			X509_ASN_ENCODING,
			NULL,		// hProv
			CERT_STORE_OPEN_EXISTING_FLAG |
			    CERT_STORE_ENUM_ARCHIVED_FLAG |
			    CERT_SYSTEM_STORE_LOCAL_MACHINE |
			    CERT_STORE_READONLY_FLAG,
			wszMY_CERTSTORE);
    if (NULL == hMyStore)
    {
        hr = myHLastError();
        _JumpError(hr, error, "CertOpenStore");
    }

    hTempMemoryStore = CertOpenStore(
				CERT_STORE_PROV_MEMORY,
				X509_ASN_ENCODING,
				NULL,
				0,
				NULL);
    if (NULL == hTempMemoryStore)
    {
        hr = myHLastError();
        _JumpError(hr, error, "CertOpenStore");
    }

    cCACertSaved = 0;
    for (i = 0; i < cCACert; i++)
    {
	hr = SaveCACertChainToMemoryStore(
			    pwszSanitizedCA,
			    i,
			    hMyStore,
			    hTempMemoryStore);
	_PrintIfError(hr, "SaveCACertChainToMemoryStore");
	if (S_FALSE != hr)
	{
	    _JumpIfError(hr, error, "SaveCACertChainToMemoryStore");

	    cCACertSaved++;
	}
    }
    if (0 == cCACertSaved)
    {
	hr = CRYPT_E_NOT_FOUND;
	_JumpError(hr, error, "SaveCACertChainToMemoryStore");
    }

    // done, have built entire chain for all CA Certs

    // GemPlus returns NTE_BAD_TYPE instead of NTE_BAD_KEY, blowing up
    // REPORT_NOT_ABLE* filtering.  if they ever get this right, we can pass
    // "[...] : EXPORT_PRIVATE_KEYS"

    hr = myPFXExportCertStore(
		hTempMemoryStore,
		&pfx,
		pwszPassword,
		fMustExportPrivateKeys? (EXPORT_PRIVATE_KEYS | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY) : 0);
    _JumpIfError(hr, error, "myPFXExportCertStore");

    hr = EncodeToFileW(
		pwszPFXFile,
		pfx.pbData,
		pfx.cbData,
		CRYPT_STRING_BINARY | (fForceOverWrite? DECF_FORCEOVERWRITE : 0));
    _JumpIfError(hr, error, "EncodeToFileW");

    if (NULL != ppwszPFXFile)
    {
	*ppwszPFXFile = pwszPFXFile;
	pwszPFXFile = NULL;
    }

error:
    if (NULL != pwszSanitizedCA)
    {
	LocalFree(pwszSanitizedCA);
    }
    if (NULL != pwszRevertCA)
    {
	LocalFree(pwszRevertCA);
    }
    if (NULL != pwszPFXFile)
    {
	LocalFree(pwszPFXFile);
    }
    if (NULL != hMyStore)
    {
	CertCloseStore(hMyStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    if (NULL != hTempMemoryStore)
    {
	CertCloseStore(hTempMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    if (NULL != pfx.pbData)
    {
	LocalFree(pfx.pbData);
    }
    if (fImpersonating)
    {
        myEnablePrivilege(SE_BACKUP_NAME, FALSE);
        RevertToSelf();
    }
    return(hr);
}


HRESULT
FindKeyUsage(
    IN DWORD cExtension,
    IN CERT_EXTENSION const *rgExtension,
    OUT DWORD *pdwUsage)
{
    HRESULT hr;
    DWORD i;
    CRYPT_BIT_BLOB *pblob = NULL;

    *pdwUsage = 0;
    for (i = 0; i < cExtension; i++)
    {
	CERT_EXTENSION const *pce;

	pce = &rgExtension[i];
	if (0 == strcmp(pce->pszObjId, szOID_KEY_USAGE))
	{
	    DWORD cb;

	    // Decode CRYPT_BIT_BLOB:

	    if (!myDecodeObject(
			    X509_ASN_ENCODING,
			    X509_KEY_USAGE,
			    pce->Value.pbData,
			    pce->Value.cbData,
			    CERTLIB_USE_LOCALALLOC,
			    (VOID **) &pblob,
			    &cb))
	    {
		hr = myHLastError();
		_JumpError(hr, error, "myDecodeObject");
	    }
	    if (1 > pblob->cbData || 8 < pblob->cUnusedBits)
	    {
		hr = E_INVALIDARG;
		_JumpError(hr, error, "Key Usage Extension too small");
	    }
	    *pdwUsage = *pblob->pbData;

	    hr = S_OK;
	    goto error;
	}
    }
    hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    _JumpError(hr, error, "no Key Usage Extension");

error:
    if (NULL != pblob)
    {
	LocalFree(pblob);
    }
    return(hr);
}


HRESULT
mySetKeySpec(
    IN CERT_CONTEXT const *pCert,
    OUT DWORD *pdwKeySpec)
{
    HRESULT hr;
    DWORD dwKeyUsage;

    *pdwKeySpec = AT_SIGNATURE;
    hr = FindKeyUsage(
		pCert->pCertInfo->cExtension,
		pCert->pCertInfo->rgExtension,
		&dwKeyUsage);
    _JumpIfError(hr, error, "FindKeyUsage");

    if (CERT_KEY_ENCIPHERMENT_KEY_USAGE & dwKeyUsage)
    {
	*pdwKeySpec = AT_KEYEXCHANGE;
    }
    hr = S_OK;

error:

    // Ignore errors because the Key Usage extension may not exist:
    hr = S_OK;

    return(hr);
}


HRESULT
myRepairKeyProviderInfo(
    IN CERT_CONTEXT const *pCert,
    IN BOOL fForceMachineKey,
    IN OUT CRYPT_KEY_PROV_INFO *pkpi)
{
    HRESULT hr;
    BOOL fModified = FALSE;

    if (0 == pkpi->dwProvType)
    {
	pkpi->dwProvType = PROV_RSA_FULL;
	fModified = TRUE;
    }
    if (0 == pkpi->dwKeySpec)
    {
	hr = mySetKeySpec(pCert, &pkpi->dwKeySpec);
	_JumpIfError(hr, error, "mySetKeySpec");

	fModified = TRUE;
    }
    if (fForceMachineKey && 0 == (CRYPT_MACHINE_KEYSET & pkpi->dwFlags))
    {
	pkpi->dwFlags |= CRYPT_MACHINE_KEYSET;
	fModified = TRUE;
    }
    if (fModified)
    {
	if (!CertSetCertificateContextProperty(
					    pCert,
					    CERT_KEY_PROV_INFO_PROP_ID,
					    0,
					    pkpi))
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "CertSetCertificateContextProperty");
	}
    }
    hr = S_OK;

error:
    return(hr);
}


HRESULT
myRepairCertKeyProviderInfo(
    IN CERT_CONTEXT const *pCert,
    IN BOOL fForceMachineKey,
    OPTIONAL OUT CRYPT_KEY_PROV_INFO **ppkpi)
{
    HRESULT hr;
    CRYPT_KEY_PROV_INFO *pkpi = NULL;

    if (NULL != ppkpi)
    {
	*ppkpi = NULL;
    }

    hr = myCertGetKeyProviderInfo(pCert, &pkpi);
    _JumpIfError2(hr, error, "myCertGetKeyProviderInfo", CRYPT_E_NOT_FOUND);

    CSASSERT(NULL != pkpi);

    hr = myRepairKeyProviderInfo(pCert, fForceMachineKey, pkpi);
    _JumpIfError(hr, error, "myRepairKeyProviderInfo");

    if (NULL != ppkpi)
    {
	*ppkpi = pkpi;
	pkpi = NULL;
    }

error:
    if (NULL != pkpi)
    {
	LocalFree(pkpi);
    }
    return(hr);
}


HRESULT
myGetCertSubjectCommonName(
    IN CERT_CONTEXT const *pCert,
    OUT WCHAR **ppwszCommonName)
{
    HRESULT hr;
    CERT_NAME_INFO *pCertNameInfo = NULL;
    DWORD cbCertNameInfo;
    WCHAR const *pwszName;

    if (!myDecodeName(
		    X509_ASN_ENCODING,
                    X509_UNICODE_NAME,
                    pCert->pCertInfo->Subject.pbData,
                    pCert->pCertInfo->Subject.cbData,
                    CERTLIB_USE_LOCALALLOC,
                    &pCertNameInfo,
                    &cbCertNameInfo))
    {
        hr = myHLastError();
	_JumpError(hr, error, "myDecodeName");
    }
    hr = myGetCertNameProperty(pCertNameInfo, szOID_COMMON_NAME, &pwszName);
    _JumpIfError(hr, error, "myGetCertNameProperty");

    *ppwszCommonName = (WCHAR *) LocalAlloc(
				    LMEM_FIXED,
				    (wcslen(pwszName) + 1) * sizeof(WCHAR));
    if (NULL == *ppwszCommonName)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    wcscpy(*ppwszCommonName, pwszName);
    hr = S_OK;

error:
    if (NULL != pCertNameInfo)
    {
	LocalFree(pCertNameInfo);
    }
    return(hr);
}


HRESULT
myGetChainArrayFromStore(
    IN HCERTSTORE hStore,
    IN BOOL fCAChain,
    IN BOOL fUserStore,
    OPTIONAL OUT WCHAR **ppwszCommonName,
    IN OUT DWORD *pcRestoreChain,
    OPTIONAL OUT RESTORECHAIN *paRestoreChain)
{
    HRESULT hr;
    CERT_CONTEXT const *pCert = NULL;
    WCHAR *pwszCommonName = NULL;
    CERT_CHAIN_PARA ChainParams;
    CRYPT_KEY_PROV_INFO *pkpi = NULL;
    DWORD iRestoreChain = 0;

    if (NULL != ppwszCommonName)
    {
	*ppwszCommonName = NULL;
    }
    if (NULL != paRestoreChain)
    {
	ZeroMemory(paRestoreChain, *pcRestoreChain * sizeof(paRestoreChain[0]));
    }

    // Look for certificates with keys.  There should be at least one.

    while (TRUE)
    {
	BOOL fMatchingKey;
	WCHAR *pwszCommonNameT;
	CERT_CHAIN_CONTEXT const *pChain;
	DWORD NameId;

	pCert = CertEnumCertificatesInStore(hStore, pCert);
	if (NULL == pCert)
	{
	    break;
	}

	if (NULL != pkpi)
	{
	    LocalFree(pkpi);
	    pkpi = NULL;
	}
	hr = myRepairCertKeyProviderInfo(pCert, !fUserStore, &pkpi);
	if (S_OK != hr)
	{
	    if (CRYPT_E_NOT_FOUND == hr)
	    {
		continue;
	    }
	    _JumpError(hr, error, "myRepairCertKeyProviderInfo");
	}
	if (NULL == pkpi || NULL == pkpi->pwszContainerName)
	{
	    continue;
	}

	hr = myVerifyPublicKey(
			pCert,
			FALSE,
			pkpi,		// pKeyProvInfo
			NULL,		// pPublicKeyInfo
			&fMatchingKey);
	_JumpIfError(hr, error, "myVerifyPublicKey");

	if (!fMatchingKey)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "Key doesn't match cert");
	}

	hr = myGetCertSubjectCommonName(pCert, &pwszCommonNameT);
	_JumpIfError(hr, error, "myGetCertSubjectCommonName");

	if (NULL == pwszCommonName)
	{
	    pwszCommonName = pwszCommonNameT;
	}
	else
	{
	    if (0 != lstrcmp(pwszCommonName, pwszCommonNameT))
	    {
		hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
		_JumpError(hr, error, "multiple CommonNames");
	    }
	    LocalFree(pwszCommonNameT);
	}
	if (fCAChain)
	{
	    hr = myGetNameId(pCert, &NameId);
	    _PrintIfError(hr, "myGetNameId");
	}
	else
	{
	    NameId = 0;
	}

	if (NULL != paRestoreChain && iRestoreChain >= *pcRestoreChain)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
	    _JumpError(hr, error, "Chain array full");
	}

	ZeroMemory(&ChainParams, sizeof(ChainParams));
	ChainParams.cbSize = sizeof(ChainParams);
	ChainParams.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;

	// Get the chain and verify the cert:

	if (!CertGetCertificateChain(
			    HCCE_LOCAL_MACHINE,		// hChainEngine
			    pCert,		// pCertContext
			    NULL,		// pTime
			    hStore,		// hAdditionalStore
			    &ChainParams,	// pChainPara
			    0,			// dwFlags
			    NULL,		// pvReserved
			    &pChain))		// ppChainContext
	{
	    hr = myHLastError();
	    _JumpIfError(hr, error, "CertGetCertificateChain");
	}
	if (NULL != paRestoreChain)
	{
	    paRestoreChain[iRestoreChain].pChain = pChain;
	    paRestoreChain[iRestoreChain].NameId = NameId;
	}
	else
	{
	    CertFreeCertificateChain(pChain);
	}
	iRestoreChain++;
    }
    if (NULL != ppwszCommonName)
    {
	*ppwszCommonName = pwszCommonName;
	pwszCommonName = NULL;
    }
    *pcRestoreChain = iRestoreChain;
    hr = S_OK;

error:
    if (S_OK != hr && NULL != paRestoreChain)
    {
	for (iRestoreChain = 0; iRestoreChain < *pcRestoreChain; iRestoreChain++)
	{
	    if (NULL != paRestoreChain[iRestoreChain].pChain)
	    {
		CertFreeCertificateChain(paRestoreChain[iRestoreChain].pChain);
		paRestoreChain[iRestoreChain].pChain = NULL;
	    }
	}
    }
    if (NULL != pwszCommonName)
    {
	LocalFree(pwszCommonName);
    }
    if (NULL != pkpi)
    {
	LocalFree(pkpi);
    }
    if (NULL != pCert)
    {
	CertFreeCertificateContext(pCert);
    }
    return(hr);
}


HRESULT
myCopyKeys(
    IN CRYPT_KEY_PROV_INFO const *pkpi,
    IN WCHAR const *pwszOldContainer,
    IN WCHAR const *pwszNewContainer,
    IN BOOL fOldUserKey,
    IN BOOL fNewUserKey,
    IN BOOL fForceOverWrite)
{
    HRESULT hr;
    HCRYPTPROV hProvOld = NULL;
    HCRYPTKEY hKeyOld = NULL;
    HCRYPTPROV hProvNew = NULL;
    HCRYPTKEY hKeyNew = NULL;
    CRYPT_BIT_BLOB PrivateKey;
    BOOL fKeyContainerNotFound = FALSE;

    PrivateKey.pbData = NULL;

    if (!myCertSrvCryptAcquireContext(
			&hProvOld,
			pwszOldContainer,
			pkpi->pwszProvName,
			pkpi->dwProvType,
			pkpi->dwFlags,
			!fOldUserKey))
    {
        hr = myHLastError();
        _JumpError(hr, error, "myCertSrvCryptAcquireContext");
    }
    if (!CryptGetUserKey(hProvOld, pkpi->dwKeySpec, &hKeyOld))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptGetUserKey");
    }
    hr = myCryptExportPrivateKey(
		    hKeyOld,
		    &PrivateKey.pbData,
		    &PrivateKey.cbData);
    _JumpIfError(hr, error, "myCryptExportPrivateKey");

    if (myCertSrvCryptAcquireContext(
			    &hProvNew,
			    pwszNewContainer,
			    pkpi->pwszProvName,
			    pkpi->dwProvType,
			    pkpi->dwFlags,
			    !fNewUserKey))
    {
	if (!fForceOverWrite)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_FILE_EXISTS);
	    _JumpErrorStr(hr, error, "Key Container Exists", pwszNewContainer);
	}

	// Delete the target key container

	CryptReleaseContext(hProvNew, 0);
        if (myCertSrvCryptAcquireContext(
			    &hProvNew,
			    pwszNewContainer,
			    pkpi->pwszProvName,
			    pkpi->dwProvType,
			    pkpi->dwFlags | CRYPT_DELETEKEYSET,
			    !fNewUserKey))
        {
            fKeyContainerNotFound = TRUE;
        }
	hProvNew = NULL;
    }
    else
    {
        fKeyContainerNotFound = TRUE;
    }

    if (!myCertSrvCryptAcquireContext(
			    &hProvNew,
			    pwszNewContainer,
			    pkpi->pwszProvName,
			    pkpi->dwProvType,
			    pkpi->dwFlags | 
			    fKeyContainerNotFound? CRYPT_NEWKEYSET : 0,
			    !fNewUserKey))
    {
	hr = myHLastError();
	_JumpError(hr, error, "myCertSrvCryptAcquireContext");
    }

    if (!CryptImportKey(
		    hProvNew,
		    PrivateKey.pbData,
		    PrivateKey.cbData,
		    NULL,		// HCRYPTKEY hPubKey
		    CRYPT_EXPORTABLE,
		    &hKeyNew))
    {
	hr = myHLastError();
	_JumpError(hr, error, "CryptImportKey");
    }

error:
    if (NULL != PrivateKey.pbData)
    {
	LocalFree(PrivateKey.pbData);
    }
    if (NULL != hKeyNew)
    {
	CryptDestroyKey(hKeyNew);
    }
    if (NULL != hProvNew)
    {
	CryptReleaseContext(hProvNew, 0);
    }
    if (NULL != hKeyOld)
    {
	CryptDestroyKey(hKeyOld);
    }
    if (NULL != hProvOld)
    {
	CryptReleaseContext(hProvOld, 0);
    }
    return(hr);
}


HRESULT
myImportChainAndKeys(
    IN WCHAR const *pwszSanitizedCA,
    IN DWORD iCert,
    IN DWORD iKey,
    IN BOOL fForceOverWrite,
    IN CERT_CHAIN_CONTEXT const *pChain,
    OPTIONAL OUT CERT_CONTEXT const **ppccNewestCA)
{
    HRESULT hr;
    BOOL fDeleted = FALSE;
    CRYPT_KEY_PROV_INFO *pkpi = NULL;
    CERT_CHAIN_ELEMENT **ppChainElement;
    WCHAR *pwszKeyContainerName = NULL;

    hr = myAllocIndexedName(pwszSanitizedCA, iKey, &pwszKeyContainerName);
    _JumpIfError(hr, error, "myAllocIndexedName");

    ppChainElement = pChain->rgpChain[0]->rgpElement;

    hr = myCertGetKeyProviderInfo(ppChainElement[0]->pCertContext, &pkpi);
    _JumpIfError(hr, error, "myCertGetKeyProviderInfo");

    if (iCert == iKey)
    {
	hr = myCopyKeys(
		    pkpi,
		    pkpi->pwszContainerName,	// pwszOldContainer
		    pwszKeyContainerName,	// pwszNewContainer
		    FALSE,			// fOldUserKey
		    FALSE,			// fNewUserKey
		    fForceOverWrite);
	_JumpIfError(hr, error, "myCopyKeys");
    }

    pkpi->pwszContainerName = pwszKeyContainerName;

    hr = mySaveChainAndKeys(
			pChain->rgpChain[0],
			wszMY_CERTSTORE,
			CERT_SYSTEM_STORE_LOCAL_MACHINE |
			    CERT_STORE_BACKUP_RESTORE_FLAG,
			pkpi,
			ppccNewestCA);
    _JumpIfError(hr, error, "mySaveChainAndKeys");

    hr = S_OK;

error:
    if (NULL != pkpi)
    {
	LocalFree(pkpi);
    }
    if (NULL != pwszKeyContainerName)
    {
        LocalFree(pwszKeyContainerName);
    }
    return(hr);
}


HRESULT
FindPFXInBackupDir(
    IN WCHAR const *pwszBackupDir,
    OUT WCHAR **ppwszPFXFile)
{
    HRESULT hr;
    HANDLE hf;
    WIN32_FIND_DATA wfd;
    WCHAR wszpath[MAX_PATH];
    WCHAR wszfile[MAX_PATH];
    DWORD cFile = 0;

    *ppwszPFXFile = NULL;

    wcscpy(wszpath, pwszBackupDir);
    wcscat(wszpath, L"\\*");
    wcscat(wszpath, wszPFXFILENAMEEXT);

    hf = FindFirstFile(wszpath, &wfd);
    if (INVALID_HANDLE_VALUE != hf)
    {
	do {
	    if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	    {
		continue;
	    }
	    cFile++;
	    wcscpy(wszfile, wfd.cFileName);
	    //printf("File: %ws\n", wszfile);
	    break;

	} while (FindNextFile(hf, &wfd));
	FindClose(hf);
    }
    if (0 == cFile)
    {
	hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
	_JumpError(hr, error, "no *.p12 files");
    }
    if (1 < cFile)
    {
	hr = HRESULT_FROM_WIN32(ERROR_DIRECTORY);
	_JumpError(hr, error, "Too many *.p12 files");
    }

    *ppwszPFXFile = (WCHAR *) LocalAlloc(
				    LMEM_FIXED,
				    (wcslen(pwszBackupDir) +
				     1 +
				     wcslen(wszfile) +
				     1) * sizeof(WCHAR));
    if (NULL == *ppwszPFXFile)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }
    wcscpy(*ppwszPFXFile, pwszBackupDir);
    wcscat(*ppwszPFXFile, L"\\");
    wcscat(*ppwszPFXFile, wszfile);
    hr = S_OK;

error:
    return(hr);
}


// Return TRUE if pcc is newer than pcc2

BOOL
IsCACertNewer(
    IN CERT_CONTEXT const *pcc,
    IN DWORD NameId,
    IN CERT_CONTEXT const *pcc2,
    IN DWORD NameId2)
{
    BOOL fNewer = FALSE;
    CERT_INFO const *pci = pcc->pCertInfo;
    CERT_INFO const *pci2 = pcc2->pCertInfo;

    if (MAXDWORD != NameId && MAXDWORD != NameId2)
    {
	if (CANAMEIDTOICERT(NameId) > CANAMEIDTOICERT(NameId2))
	{
	    fNewer = TRUE;
	}
    }
    else
    if (CompareFileTime(&pci->NotAfter, &pci2->NotAfter) > 0)
    {
	fNewer = TRUE;
    }

#if 0
    HRESULT hr;
    WCHAR *pwszDate = NULL;
    WCHAR *pwszDate2 = NULL;

    hr = myGMTFileTimeToWszLocalTime(&pci->NotAfter, &pwszDate);
    _PrintIfError(hr, "myGMTFileTimeToWszLocalTime");

    hr = myGMTFileTimeToWszLocalTime(&pci2->NotAfter, &pwszDate2);
    _PrintIfError(hr, "myGMTFileTimeToWszLocalTime");

    printf(
	"%u.%u %ws is %wsnewer than %u.%u %ws\n",
	CANAMEIDTOICERT(NameId),
	CANAMEIDTOIKEY(NameId),
	pwszDate,
	fNewer? L"" : L"NOT ",
	CANAMEIDTOICERT(NameId2),
	CANAMEIDTOIKEY(NameId2),
	pwszDate2);

    if (NULL != pwszDate) LocalFree(pwszDate);
    if (NULL != pwszDate2) LocalFree(pwszDate2);
#endif

    return(fNewer);
}


#if 0
VOID
DumpChainArray(
    IN char const *psz,
    IN DWORD cCACert,
    IN OUT RESTORECHAIN *paRestoreChain)
{
    HRESULT hr;
    DWORD i;
    
    printf("\n%hs:\n", psz);
    for (i = 0; i < cCACert; i++)
    {
	WCHAR *pwszDate;
	
	hr = myGMTFileTimeToWszLocalTime(
	    &paRestoreChain[i].pChain->rgpChain[0]->rgpElement[0]->pCertContext->pCertInfo->NotBefore,
	    &pwszDate);
	_PrintIfError(hr, "myGMTFileTimeToWszLocalTime");

	printf(
	    " %u: %u.%u %ws",
	    i,
	    CANAMEIDTOICERT(paRestoreChain[i].NameId),
	    CANAMEIDTOIKEY(paRestoreChain[i].NameId),
	    pwszDate);


	if (NULL != pwszDate) LocalFree(pwszDate);

	hr = myGMTFileTimeToWszLocalTime(
	    &paRestoreChain[i].pChain->rgpChain[0]->rgpElement[0]->pCertContext->pCertInfo->NotAfter,
	    &pwszDate);
	_PrintIfError(hr, "myGMTFileTimeToWszLocalTime");

	printf(" -- %ws\n", pwszDate);

	if (NULL != pwszDate) LocalFree(pwszDate);
    }
    printf("\n");
}
#endif


HRESULT
SortCACerts(
    IN DWORD cCACert,
    IN OUT RESTORECHAIN *paRestoreChain)
{
    HRESULT hr;
    DWORD i;
    DWORD j;

#if 0
    DumpChainArray("Start", cCACert, paRestoreChain);
#endif

    for (i = 0; i < cCACert; i++)
    {
	for (j = i + 1; j < cCACert; j++)
	{
	    CERT_CHAIN_CONTEXT const *pChain;
	    DWORD NameId;
	    DWORD NameId2;
	    CERT_CONTEXT const *pcc;
	    CERT_CONTEXT const *pcc2;

	    pChain = paRestoreChain[i].pChain;
	    NameId = paRestoreChain[i].NameId;
	    NameId2 = paRestoreChain[j].NameId;

	    pcc = pChain->rgpChain[0]->rgpElement[0]->pCertContext;
	    pcc2 = paRestoreChain[j].pChain->rgpChain[0]->rgpElement[0]->pCertContext;

#if 0
	    printf(
		"%u(%u.%u) %u(%u.%u): ",
		i,
		CANAMEIDTOIKEY(NameId),
		CANAMEIDTOICERT(NameId),
		j,
		CANAMEIDTOIKEY(NameId2),
		CANAMEIDTOICERT(NameId2));
#endif

	    if (IsCACertNewer(pcc, NameId, pcc2, NameId2))
	    {
		paRestoreChain[i] = paRestoreChain[j];
		paRestoreChain[j].pChain = pChain;
		paRestoreChain[j].NameId = NameId;
	    }
	}
    }
#if 0
    DumpChainArray("End", cCACert, paRestoreChain);
#endif

    hr = S_OK;

//error:
    return(hr);
}


HRESULT
myDeleteGuidKeys(
    IN HCERTSTORE hStorePFX,
    IN BOOL fMachineKeySet)
{
    HRESULT hr;
    CERT_CONTEXT const *pCert = NULL;
    CRYPT_KEY_PROV_INFO *pkpi = NULL;

    // Look for certificates with keys, and delete all key containers with
    // names that look like GUIDs.

    while (TRUE)
    {
	HCRYPTPROV hProv;

	pCert = CertEnumCertificatesInStore(hStorePFX, pCert);
	if (NULL == pCert)
	{
	    break;
	}

	if (NULL != pkpi)
	{
	    LocalFree(pkpi);
	    pkpi = NULL;
	}
	hr = myRepairCertKeyProviderInfo(pCert, FALSE, &pkpi);
	if (S_OK == hr &&
	    NULL != pkpi->pwszContainerName &&
	    wcLBRACE == *pkpi->pwszContainerName)
	{
	    if (myCertSrvCryptAcquireContext(
			    &hProv,
			    pkpi->pwszContainerName,
			    pkpi->pwszProvName,
			    pkpi->dwProvType,
			    pkpi->dwFlags | CRYPT_DELETEKEYSET,
			    fMachineKeySet))
	    {
		DBGPRINT((
		    DBG_SS_CERTLIBI,
		    "myDeleteGuidKeys(%ws, %ws)\n",
		    fMachineKeySet? L"Machine" : L"User",
		    pkpi->pwszContainerName));
	    }
	}
    }
    hr = S_OK;

//error:
    if (NULL != pkpi)
    {
	LocalFree(pkpi);
    }
    if (NULL != pCert)
    {
	CertFreeCertificateContext(pCert);
    }
    return(hr);
}


HRESULT
myCertServerImportPFX(
    IN WCHAR const *pwszBackupDirOrPFXFile,
    IN WCHAR const *pwszPassword,
    IN BOOL fForceOverWrite,
    OPTIONAL OUT WCHAR **ppwszCommonName,
    OPTIONAL OUT WCHAR **ppwszPFXFile,
    OPTIONAL OUT CERT_CONTEXT const **ppccNewestCA)
{
    HRESULT hr;
    CRYPT_DATA_BLOB pfx;
    HCERTSTORE hStorePFX = NULL;
    WCHAR *pwszCommonName = NULL;
    WCHAR *pwszSanitizedName = NULL;
    RESTORECHAIN *paRestoreChain = NULL;
    WCHAR *pwszPFXFile = NULL;
    DWORD FileAttr;
    BOOL fImpersonating = FALSE;
    DWORD cCACert;
    DWORD iCert;

    pfx.pbData = NULL;

    if (NULL != ppwszCommonName)
    {
        *ppwszCommonName = NULL;
    }
    if (NULL != ppwszPFXFile)
    {
        *ppwszPFXFile = NULL;
    }
    if (NULL != ppccNewestCA)
    {
        *ppccNewestCA = NULL;
    }

    if (!ImpersonateSelf(SecurityImpersonation))
    {
        hr = myHLastError();
        _JumpError(hr, error, "ImpersonateSelf");
    }
    fImpersonating = TRUE;

    hr = myEnablePrivilege(SE_RESTORE_NAME, TRUE);
    _JumpIfError(hr, error, "myEnablePrivilege");

    hr = myEnablePrivilege(SE_BACKUP_NAME, TRUE);
    _JumpIfError(hr, error, "myEnablePrivilege");

    FileAttr = GetFileAttributes(pwszBackupDirOrPFXFile);
    if (MAXDWORD == FileAttr)
    {
        hr = myHLastError();
        _JumpError(hr, error, "GetFileAttributes");
    }

    if (FILE_ATTRIBUTE_DIRECTORY & FileAttr)
    {
        hr = FindPFXInBackupDir(pwszBackupDirOrPFXFile, &pwszPFXFile);
        _JumpIfError(hr, error, "FindPFXInBackupDir");
    }
    else
    {
	hr = myDupString(pwszBackupDirOrPFXFile, &pwszPFXFile);
	_JumpIfError(hr, error, "myDupString");
    }

    hr = DecodeFileW(pwszPFXFile, &pfx.pbData, &pfx.cbData, CRYPT_STRING_ANY);
    _JumpIfError(hr, error, "DecodeFileW");

    CSASSERT(NULL != pfx.pbData);

    if (!PFXIsPFXBlob(&pfx))
    {
        hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
        _JumpError(hr, error, "PFXIsPFXBlob");
    }

    hStorePFX = myPFXImportCertStore(&pfx, pwszPassword, CRYPT_EXPORTABLE);
    if (NULL == hStorePFX)
    {
        hr = myHLastError();
        _JumpError(hr, error, "myPFXImportCertStore");
    }

    cCACert = 0;
    hr = myGetChainArrayFromStore(
			    hStorePFX,
			    TRUE,		// fCAChain
			    FALSE,		// fUserStore
			    &pwszCommonName,
			    &cCACert,
			    NULL);
    _JumpIfError(hr, error, "myGetChainArrayFromStore");

    if (0 == cCACert)
    {
        hr = HRESULT_FROM_WIN32(CRYPT_E_SELF_SIGNED);
        _JumpError(hr, error, "myGetChainArrayFromStore <no chain>");
    }

    paRestoreChain = (RESTORECHAIN *) LocalAlloc(
					LMEM_FIXED | LMEM_ZEROINIT,
					cCACert * sizeof(paRestoreChain[0]));
    if (NULL == paRestoreChain)
    {
	hr = E_OUTOFMEMORY;
	_JumpError(hr, error, "LocalAlloc");
    }

    hr = myGetChainArrayFromStore(
			    hStorePFX,
			    TRUE,		// fCAChain
			    FALSE,		// fUserStore
			    NULL,
			    &cCACert,
			    paRestoreChain);
    _JumpIfError(hr, error, "myGetChainArrayFromStore");

    hr = SortCACerts(cCACert, paRestoreChain);
    _JumpIfError(hr, error, "SortCACerts");

    hr = mySanitizeName(pwszCommonName, &pwszSanitizedName);
    _JumpIfError(hr, error, "mySanitizeName");

    for (iCert = 0; iCert < cCACert; iCert++)
    {
	CERT_CHAIN_CONTEXT const *pChain = paRestoreChain[iCert].pChain;
	DWORD iKey;
	CERT_PUBLIC_KEY_INFO *pPublicKeyInfo;

	if (1 > pChain->cChain)
	{
	    hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
	    _JumpError(hr, error, "No Chain Context");
	}

	// Compute iKey by comparing this public key to the public keys
	// of all certs in the array already processed.

	pPublicKeyInfo = &pChain->rgpChain[0]->rgpElement[0]->pCertContext->pCertInfo->SubjectPublicKeyInfo;

	for (iKey = 0; iKey < iCert; iKey++)
	{
	    if (CertComparePublicKeyInfo(
				    X509_ASN_ENCODING,
				    pPublicKeyInfo,
				    &paRestoreChain[iKey].pChain->rgpChain[0]->rgpElement[0]->pCertContext->pCertInfo->SubjectPublicKeyInfo))
	    {
		// by design, CertComparePublicKeyInfo doesn't set last error!

		break;
	    }
	}
	DBGPRINT((
	    DBG_SS_CERTLIB,
	    "Import: %u.%u -- %u.%u\n",
	    iCert,
	    iKey,
	    CANAMEIDTOICERT(paRestoreChain[iCert].NameId),
	    CANAMEIDTOIKEY(paRestoreChain[iCert].NameId)));

	// Retrieve the cert context for the newest CA cert chain in the PFX
	// we are importing.  We must return a cert context with the new
	// key prov info, not the PFX cert context with a GUID key container.

	hr = myImportChainAndKeys(
			    pwszSanitizedName,
			    iCert,
			    iKey,
			    fForceOverWrite,
			    pChain,
			    iCert + 1 == cCACert? ppccNewestCA : NULL);
	_JumpIfError(hr, error, "myImportChainAndKeys");
    }

    if (NULL != ppwszCommonName)
    {
        *ppwszCommonName = pwszCommonName;
        pwszCommonName = NULL;
    }
    if (NULL != ppwszPFXFile)
    {
        *ppwszPFXFile = pwszPFXFile;
        pwszPFXFile = NULL;
    }
    hr = S_OK;

error:
    if (NULL != paRestoreChain)
    {
        for (iCert = 0; iCert < cCACert; iCert++)
	{
	    if (NULL != paRestoreChain[iCert].pChain)
	    {
		CertFreeCertificateChain(paRestoreChain[iCert].pChain);
	    }
	}
	LocalFree(paRestoreChain);
    }
    if (NULL != pwszPFXFile)
    {
        LocalFree(pwszPFXFile);
    }
    if (NULL != pwszCommonName)
    {
        LocalFree(pwszCommonName);
    }
    if (NULL != pwszSanitizedName)
    {
        LocalFree(pwszSanitizedName);
    }
    if (NULL != hStorePFX)
    {
        myDeleteGuidKeys(hStorePFX, TRUE);
	CertCloseStore(hStorePFX, CERT_CLOSE_STORE_CHECK_FLAG);
    }
    if (NULL != pfx.pbData)
    {
        LocalFree(pfx.pbData);
    }
    if (fImpersonating)
    {
        myEnablePrivilege(SE_RESTORE_NAME, FALSE);
        myEnablePrivilege(SE_BACKUP_NAME,  FALSE);
        RevertToSelf();
    }
    return(hr);
}