What I am ultimately trying to do is either add an entirely new subkey with a name called "XtraFileLogging" with some default value, currently it's "NOTHING!!!", or I want to extract the value from the subkey named "XtraFileLogging".
Why am I having to call SetValue after a call to SetKeyValue when trying to add an entirely new subkey?
The following code works but it seems like I am using an extra step with a call to SetValue seeing as how SetKeyValue does the following https://msdn.microsoft.com/en-us/library/e2ey539x.aspx
CRegKey oRegKey;
bool setKeyWorked = false;
if (oRegKey.Open(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\EXTRA\\PROG2\\CONFIG")) == ERROR_SUCCESS)
{
LPCTSTR keyName = _T("XtraFileLogging");
LPCTSTR keysValue = _T("NOTHING!!!");
if (oRegKey.SetKeyValue(keyName, keysValue) == ERROR_SUCCESS)
{
setKeyWorked = true;
if (oRegKey.SetValue(keysValue, keyName) == ERROR_SUCCESS)
{
setKeyWorked = true;
}
else
{
setKeyWorked = false;
}
}
}
oRegKey.Close();
Why is it when i try either of the following SetKeyValue statements without the extra call to SetValue that the key doesn't show up?
if (oRegKey.SetKeyValue(keyName, keysValue) == ERROR_SUCCESS)
{
setKeyWorked = true;
}
Or
if (oRegKey.SetKeyValue(keyName, keysValue, keyName) == ERROR_SUCCESS)
{
setKeyWorked = true;
}
I even attach to the process and debug it and every time I can see that setKeyWorked is equal to true. Unfortunately without the extra call to SetValue afterwards the key never ends up getting created which I verified using regedit.
Related
Here is a method:
// Override to be notified about every file processed
bool MultiActionsNext(LPCTSTR lpszFileInZip = NULL)
{
if (CZipActionCallback::MultiActionsNext())
{
CMultiActionsInfo* pMulti = GetMultiActionsInfo();
CString strProgress, strPercentComplete;
strPercentComplete.Format(_T("%d%%"),
theApp.CalculatePercent(pMulti->m_uFilesProcessed, pMulti->m_uTotalFilesToProcess));
if (lpszFileInZip != NULL)
{
strProgress = lpszFileInZip;
}
else
{
strProgress.Format(_T("Files Processed: %u of %u"),
pMulti->m_uFilesProcessed, pMulti->m_uTotalFilesToProcess);
}
if (m_pWndProgressText != nullptr)
{
m_pWndProgressText->SetWindowText(strProgress);
m_pWndProgressText->UpdateWindow();
}
if (m_pWndProgressPercentText != nullptr)
{
m_pWndProgressPercentText->SetWindowText(strPercentComplete);
m_pWndProgressPercentText->UpdateWindow();
}
if (m_pProgressCtrl != nullptr)
m_pProgressCtrl->SetPos(pMulti->m_uFilesProcessed);
return true;
}
else
// Processing was requested to be stopped.
// It won't happen in this sample code, because
// the Callback method always returns true.
return false;
}
My problem is with this line of code:
m_pWndProgressText->SetWindowText(strProgress);
If I am in DEBUG mode the result is fine:
But when I am in RELEASE mode the result is garbage:
If I put a break point in this function for lpszFileInZip:
Debug:
Release:
I can't work out why one is OK and one is not.
In Release 32 bit mode I do now notice that I get an exception that indicates the passed in parameter is not valid:
I am debugging an issue with WinDbg which I can consistently produce. The problem is when I run the executable with WinDbg to debug it, the issue can't be reproduced. What could be the reason?
Here is the code the behaves differently:
CWnd* pWnd = GetDlgItem(IDOKCANCEL);
if(pWnd)
{
CString sOK;
sOK.LoadString(IDS_OK);
pWnd->SetWindowText(sOK);
}
Here the button text is updated properly when I run with WinDbg but it is not updated when I run it normally (which is the bug).
Update
Like I said in comments, the issue is not with the code above because it's doesn't even get called. The operation is done in a worker thread which sends update messages to this dialog. The final message that executes the above code is never send do it so the above code is never executed.
Why the worker thread doesn't send this message is interesting. It ges locked on a critical section while opening a database. WinDbg tells me that the main thread is the owner of that critical section but I can't see from call stack or any other way where does it failed to unlock the critical section.
What complicates the problem is that it works fine if I run it with debugger. I added log output but it also starts to works fine with this change.
The only way I can catch it with a debugger is when I run it normal mode, produce the problem, then attach the debugger and it shows me its locked on the critical section. It shows the main thread is the owner of that critical section but it not clear why it is in locked state. The critical section is simply locked and unlocked in one function and its out of there.
Update 2
I am using the critical section only in one file in my entire project and there in only two functions (when it opens database and recordset).
BOOL CADODatabase::Open(LPCTSTR lpstrConnection, LPCTSTR lpstrUserID, LPCTSTR lpstrPassword)
{
CString database = GetSourceDatabase( lpstrConnection, NULL );
// get the appropriate critical section based on database
g_dbCriticalSection = GetDbCriticalSection( database );
if( g_dbCriticalSection)
g_dbCriticalSection->Lock();
HRESULT hr = S_OK;
if(IsOpen())
Close();
if(wcscmp(lpstrConnection, _T("")) != 0)
m_strConnection = lpstrConnection;
ASSERT(!m_strConnection.IsEmpty());
try
{
if(m_nConnectionTimeout != 0)
m_pConnection->PutConnectionTimeout(m_nConnectionTimeout);
hr = m_pConnection->Open(_bstr_t(m_strConnection), _bstr_t(lpstrUserID), _bstr_t(lpstrPassword), NULL);
if( g_dbCriticalSection)
g_dbCriticalSection->Unlock();
return hr == S_OK;
}
catch(_com_error &e)
{
dump_com_error(e);
if( g_dbCriticalSection)
g_dbCriticalSection->Unlock();
return FALSE;
}
}
The 2nd function has other visible imperfections but please ignore that, its legacy code.
BOOL CADORecordset::Open(_ConnectionPtr mpdb, LPCTSTR lpstrExec, int nOption)
{
BSTR bstrConnString;
m_pConnection->get_ConnectionString(&bstrConnString);
CString database = GetSourceDatabase( bstrConnString, m_pConnection );
g_dbCriticalSection = GetDbCriticalSection( database );
if( g_dbCriticalSection)
g_dbCriticalSection->Lock();
Close();
if(wcscmp(lpstrExec, _T("")) != 0)
m_strQuery = lpstrExec;
ASSERT(!m_strQuery.IsEmpty());
if(m_pConnection == NULL)
m_pConnection = mpdb;
m_strQuery.TrimLeft();
BOOL bIsSelect = m_strQuery.Mid(0, _tcslen(_T("Select "))).CompareNoCase(_T("select ")) == 0 && nOption == openUnknown;
int maxRetries = 10;
bool bContinue = true;
CursorTypeEnum adCursorType = adOpenStatic;
if (!m_bSQLEngine)
{
// MDB Engine
adCursorType = adOpenStatic;
m_pConnection->CursorLocation = adUseClient;
}
else
{
// SQL Engine
adCursorType = adOpenDynamic;
m_pConnection->CursorLocation = adUseServer;
}
int currentCommandTimeout = m_pConnection->CommandTimeout;
if( g_dbCriticalSection)
g_dbCriticalSection->Unlock();
for (int iRetry = 0; (iRetry < maxRetries) && bContinue; iRetry++)
{
try
{
// we just use an auto lock object so it is unlocked automatically, it uses same
// critical section object.
if( g_dbCriticalSection)
g_dbCriticalSection->Lock();
int newCommandTimeout = currentCommandTimeout + 15 * iRetry;
m_pConnection->CommandTimeout = newCommandTimeout;
if(bIsSelect || nOption == openQuery || nOption == openUnknown)
{
m_pRecordset->Open((LPCTSTR)m_strQuery, _variant_t((IDispatch*)mpdb, TRUE),
adCursorType, adLockOptimistic, adCmdUnknown);
}
else if(nOption == openTable)
{
m_pRecordset->Open((LPCTSTR)m_strQuery, _variant_t((IDispatch*)mpdb, TRUE),
adOpenDynamic, adLockOptimistic, adCmdTable);
}
else if(nOption == openStoredProc)
{
m_pCmd->ActiveConnection = mpdb;
m_pCmd->CommandText = _bstr_t(m_strQuery);
m_pCmd->CommandType = adCmdStoredProc;
m_pRecordset = m_pCmd->Execute(NULL, NULL, adCmdText);
}
else
{
TRACE( _T("Unknown parameter. %d"), nOption);
if( g_dbCriticalSection)
g_dbCriticalSection->Unlock();
return FALSE;
}
if( g_dbCriticalSection)
g_dbCriticalSection->Unlock();
bContinue = false;
}
catch(_com_error &e)
{
if( g_dbCriticalSection)
g_dbCriticalSection->Unlock();
dump_com_error_without_exception(e, _T("Open"));
// retry Query timeout
CString szDescription;
_bstr_t bstrDescription(e.Description());
szDescription.Format( _T("%s"), (LPCTSTR)bstrDescription);
if ((szDescription.Find(_T("Query timeout expired")) == -1) || (iRetry == maxRetries - 1))
{
m_pConnection->CommandTimeout = currentCommandTimeout;
throw CADOException(e.Error(), e.Description());
}
Sleep (1000);
bContinue = true;
}
}
m_pConnection->CommandTimeout = currentCommandTimeout;
return m_pRecordset != NULL && m_pRecordset->GetState()!= adStateClosed;
}
For the sake of completeness, the above calls this function:
static CCriticalSection* GetDbCriticalSection(const CString& database)
{
// For now we only care about one database and its corresponding critical section
if (database.CompareNoCase( _T("Alr") ) == 0)
return &g_csAlrDb; // g_csAlrDb is defined static global in this file
else
return 0;
}
The Open() function gets called for various databases, I am only locking guarding access to one database. As you can see there is corresponding lock/unlocks so not sure how does code comes up of these functions leave th critical section locked. Could it be because of MFC issue?
In my case, most of the time, when C++ software behaves different between debug and release versions, it's because of uninitialized variables, different libraries linked, or compiler optimizations backfiring.
To trace the bug, try evaluating variables and function return values, i.e. LoadString, for example with AfxMessageBox().
I have an application that tries to verify the mmc.exe (services) signature. (the context of the application I think is irrelevant) I am trying with winapi function which both fails with
WinVerifyTrust. I get TRUST_E_BAD_DIGEST when I am trying with verification from catalog, and
TRUST_E_NOSIGNATURE when trying from file info. it is very important to mention that my function succeeds on win7, XP but fails on win8.
this is the code snippet for the function
CATALOG_INFO InfoStruct = {0};
InfoStruct.cbStruct = sizeof(CATALOG_INFO);
WINTRUST_CATALOG_INFO WintrustCatalogStructure = {0};
WintrustCatalogStructure.cbStruct = sizeof(WINTRUST_CATALOG_INFO);
WINTRUST_FILE_INFO WintrustFileStructure = {0};
WintrustFileStructure.cbStruct = sizeof(WINTRUST_FILE_INFO);
GUID ActionGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2;
//Get a context for signature verification.
HCATADMIN Context = NULL;
if(!::CryptCATAdminAcquireContext(&Context, NULL, 0) ){
return false;
}
//Open file.
cx_handle hFile(::CreateFileW(filename_.c_str(), GENERIC_READ, 7, NULL, OPEN_EXISTING, 0, NULL));
if( INVALID_HANDLE_VALUE == (HANDLE)hFile )
{
CryptCATAdminReleaseContext(Context, 0);
return false;
}
//Get the size we need for our hash.
DWORD HashSize = 0;
::CryptCATAdminCalcHashFromFileHandle(hFile, &HashSize, NULL, 0);
if( HashSize == 0 )
{
//0-sized has means error!
::CryptCATAdminReleaseContext(Context, 0);
return false;
}
//Allocate memory.
buffer hashbuf(HashSize);
//Actually calculate the hash
if( !CryptCATAdminCalcHashFromFileHandle(hFile, &HashSize, hashbuf.data, 0) )
{
CryptCATAdminReleaseContext(Context, 0);
return false;
}
//Convert the hash to a string.
buffer MemberTag(((HashSize * 2) + 1) * sizeof(wchar_t));
for( unsigned int i = 0; i < HashSize; i++ ){
swprintf(&((PWCHAR)MemberTag.data)[i * 2], L"%02X", hashbuf.data[i ]);
}
//Get catalog for our context.
HCATINFO CatalogContext = CryptCATAdminEnumCatalogFromHash(Context, hashbuf, HashSize, 0, NULL);
if ( CatalogContext )
{
//If we couldn't get information
if ( !CryptCATCatalogInfoFromContext(CatalogContext, &InfoStruct, 0) )
{
//Release the context and set the context to null so it gets picked up below.
CryptCATAdminReleaseCatalogContext(Context, CatalogContext, 0);
CatalogContext = NULL;
}
}
//If we have a valid context, we got our info.
//Otherwise, we attempt to verify the internal signature.
WINTRUST_DATA WintrustStructure = {0};
WintrustStructure.cbStruct = sizeof(WINTRUST_DATA);
if( !CatalogContext )
{
load_signature_verification_from_file_info(WintrustFileStructure, WintrustStructure);
}
else
{
load_signature_verification_from_catalog(WintrustStructure, WintrustCatalogStructure, InfoStruct, MemberTag);
}
//Call our verification function.
long verification_res = ::WinVerifyTrust(0, &ActionGuid, &WintrustStructure);
//Check return.
bool is_success = SUCCEEDED(verification_res) ? true : false;
// if failed with CatalogContext, try with FILE_INFO
if(!is_success && CatalogContext && verification_res != TRUST_E_NOSIGNATURE)
{
//warning2(L"Failed verification with Catalog Context: 0x%x %s ; Retrying with FILE_INFO.", verification_res, (const wchar_t*)format_last_error(verification_res));
load_signature_verification_from_file_info(WintrustFileStructure, WintrustStructure);
verification_res = ::WinVerifyTrust(0, &ActionGuid, &WintrustStructure);
is_success = SUCCEEDED(verification_res) ? true : false;
}
if(perr && !is_success && verification_res != TRUST_E_NOSIGNATURE)
{
perr->code = verification_res;
perr->description = format_last_error(verification_res);
}
//Free context.
if( CatalogContext ){
::CryptCATAdminReleaseCatalogContext(Context, CatalogContext, 0);
}
//If we successfully verified, we need to free.
if( is_success )
{
WintrustStructure.dwStateAction = WTD_STATEACTION_CLOSE;
::WinVerifyTrust(0, &ActionGuid, &WintrustStructure);
}
::CryptCATAdminReleaseContext(Context, 0);
return is_success;
I don't think any thing had changed in this function from win7 to win 8 so what could possibly go wrong?
UPDATE
I did notice that my function does work for task manager at win 8.
but again for the mmc it does not work.
It appears that your general approach is correct and the functions themselves haven't changed. However there are subtle changes; namely the data on which they operate has changed. The hashes stored for files on Windows 8, according to comments on CryptCATAdminCalcHashFromFileHandle, are calculated using SHA-256 hashes.
The SHA-256 hashing algorithm is not supported by CryptCATAdminCalcHashFromFileHandle, so you must update the code to use CryptCATAdminAcquireContext2 and CryptCATAdminCalcHashFromFileHandle2 on Windows 8; the former allows you to acquire a HCATADMIN with a specified hash algorithm, and the latter allows using that HCATADMIN.
(Interestingly, WINTRUST_CATALOG_INFO also points this direction with its HCATADMIN hCatAdmin member, documented as "Windows 8 and Windows Server 2012: Support for this member begins.")
I am trying to get at the 'UILevel' MSI property from within a C++ custom action in order to determine whether or not the user is running in 'no UI mode', but am not having much luck. The function I am calling is passed the MSIHANDLE from a function which I export in my DLL (which may be either a 'deferred' or 'firstsequence' action). What I'm seeing is that MsiGetPropertyW is always returning ERROR_MORE_DATA and the trueLength field is always 0. Here is my code:
bool runningInNoUIMode(MSIHANDLE hInstall)
{
unsigned long nBufLen = 64UL;
WCHAR *wszValue = new WCHAR[nBufLen];
DWORD trueLength = 0UL;
UINT result = ::MsiGetPropertyW(hInstall, L"UILevel", L"", &trueLength); // Get the size of the property value first to see if there is enough storage allocated.
if (ERROR_MORE_DATA == result || nBufLen <= trueLength)
{
if (NULL != wszValue)
{
delete [] wszValue;
}
// Allocate more memory for the property adding one for the null terminator.
nBufLen = trueLength + 1;
wszValue = new WCHAR[nBufLen];
}
if (NULL == wszValue)
{
WcaLog(LOGMSG_STANDARD, "Unable to determine the user interface level the MSI is being run with because we were unable to allocate storage for accessing the 'UILevel' property.");
return false;
}
memset(wszValue, L'\0', nBufLen * sizeof(WCHAR));
result = ::MsiGetPropertyW(hInstall, L"UILevel", wszValue, &trueLength);
if (ERROR_SUCCESS != result)
{
WcaLog(LOGMSG_STANDARD, "Unable to determine the user interface level the MSI is being run with, error code = '%lu'.", result);
delete [] wszValue;
return false;
}
if (0 == wcscmp(L"2", wszValue)) // INSTALLUILEVEL_NONE == 2
{
delete [] wszValue;
return true;
}
delete [] wszValue;
return false;
}
I believe I can work around this for now by passing the 'UILevel' property through WiX and checking for it that way in the C++, but I am curious what the problem here is as well.
I'm using Visual Studio/Visual C++ 2010 on Windows 7 with WiX 3.5.2519.
Thanks for any assistance you can provide!
Another way of making this simpler is to use the MsiEvaluateCondition function.
BOOL bUI = MsiEvaluateCondition(L"UILevel<3");
in C# using Microsoft.Deployment.WindowsIntaller (DTF) it's:
var uiLevel = session["UILevel"];
In C++ there's a sample at MsiGetProperty function:
UINT __stdcall MyCustomAction(MSIHANDLE hInstall)
{
TCHAR* szValueBuf = NULL;
DWORD cchValueBuf = 0;
UINT uiStat = MsiGetProperty(hInstall, TEXT("MyProperty"), TEXT(""), &cchValueBuf);
//cchValueBuf now contains the size of the property's string, without null termination
if (ERROR_MORE_DATA == uiStat)
{
++cchValueBuf; // add 1 for null termination
szValueBuf = new TCHAR[cchValueBuf];
if (szValueBuf)
{
uiStat = MsiGetProperty(hInstall, TEXT("MyProperty"), szValueBuf, &cchValueBuf);
}
}
if (ERROR_SUCCESS != uiStat)
{
if (szValueBuf != NULL)
delete[] szValueBuf;
return ERROR_INSTALL_FAILURE;
}
// custom action uses MyProperty
// ...
delete[] szValueBuf;
return ERROR_SUCCESS;
}
Thanks to #DanielGehriger, we figured out that the problem isn't with the code, but with the scheduling for the custom action. The UILevel MSI property is simply not available when running a deferred custom action (I found that the code worked correctly for a custom action scheduled for firstsequence). I have worked around this limitation by explicitly passing it on custom action data using WiX:
<CustomAction Id="CustomAction.SetProperty" Property="CustomActionCall"
Value="UILEVEL=[UILevel];" />
and then checking for this in the C++ with WcaIsPropertySet and WcaGetProperty. Note that the character case of the property name between square brackets matters here.
I am trying to use the Microsoft 'Crypt...' functions to generate an MD5 hash key from the data that is added to the hash object. I am also trying to use the 'CryptSetHashParam' to set the hash object to a particular hash value before adding data to it.
According to the Microsoft documentation (if I am interpreting it correctly), you should be able to do this by creating a duplicate hash of the original object, use the 'CryptGetHashParam' function to retrieve the hash size then use 'CryptSetHashParam' on the original object to set the hash value accordingly. I am aware that after using 'CryptGetHashParam' you are unable to add additional data to a hash object (which is why I thought you needed to create a duplicate), but I can't add data to either the original hash object or the duplicate hash object after using either 'CryptGetHashParam' (as expected), or 'CryptSetHashParam' (which I didn't expect).
Below are code extracts of the class I am writing and an example of how I am using the class functions:
The result I get after running the code is:
"AddDataToHash function failed - Errorcode: 2148073484.", which translates to: "Hash not valid for use in specified state.".
I've tried many different ways to try and get this working as intended, but the result is always the same. I accept that I am doing something wrong, but I can't see what it is I'm doing wrong. Any ideas please?
CLASS CONSTRUCTOR INITIALISATION.
CAuthentication::CAuthentication()
{
m_dwLastError = ERROR_SUCCESS;
m_hCryptProv = NULL;
m_hHash = NULL;
m_hDuplicateHash = NULL;
if(!CryptAcquireContext(&m_hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
{
m_dwLastError = GetLastError();
if (m_dwLastError == 0x80090016 )
{
if(!CryptAcquireContext(&m_hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET))
{
m_dwLastError = GetLastError();
m_hCryptProv = NULL;
}
}
}
if(!CryptCreateHash(m_hCryptProv, CALG_MD5, 0, 0, &m_hHash))
{
m_dwLastError = GetLastError();
m_hHash = NULL;
}
}
FUNCTION USED TO SET THE HASH VALUE OF THE HASH OBJECT.
bool CAuthentication::SetHashKeyString(char* pszKeyBuffer)
{
bool bHashStringSet = false;
DWORD dwHashSize = 0;
DWORD dwHashLen = sizeof(DWORD);
BYTE byHash[DIGITAL_SIGNATURE_LENGTH / 2]={0};
if(pszKeyBuffer != NULL && strlen(pszKeyBuffer) == DIGITAL_SIGNATURE_LENGTH)
{
if(CryptDuplicateHash(m_hHash, NULL, 0, &m_hDuplicateHash))
{
if(CryptGetHashParam(m_hDuplicateHash, HP_HASHSIZE, reinterpret_cast<BYTE*>(&dwHashSize), &dwHashLen, 0))
{
if (dwHashSize == DIGITAL_SIGNATURE_LENGTH / 2)
{
char*pPtr = pszKeyBuffer;
ULONG ulTempVal = 0;
for(ULONG ulIdx = 0; ulIdx < dwHashSize; ulIdx++)
{
sscanf(pPtr, "%02X", &ulTempVal);
byHash[ulIdx] = static_cast<BYTE>(ulTempVal);
pPtr+= 2;
}
if(CryptSetHashParam(m_hHash, HP_HASHVAL, &byHash[0], 0))
{
bHashStringSet = true;
}
else
{
pszKeyBuffer = "";
m_dwLastError = GetLastError();
}
}
}
else
{
m_dwLastError = GetLastError();
}
}
else
{
m_dwLastError = GetLastError();
}
}
if(m_hDuplicateHash != NULL)
{
CryptDestroyHash(m_hDuplicateHash);
}
return bHashStringSet;
}
FUNCTION USED TO ADD DATA FOR HASHING.
bool CAuthentication::AddDataToHash(BYTE* pbyHashBuffer, ULONG ulLength)
{
bool bHashDataAdded = false;
if(CryptHashData(m_hHash, pbyHashBuffer, ulLength, 0))
{
bHashDataAdded = true;
}
else
{
m_dwLastError = GetLastError();
}
return bHashDataAdded;
}
MAIN FUNCTION CLASS USAGE:
CAuthentication auth;
.....
auth.SetHashKeyString("0DD72A4F2B5FD48EF70B775BEDBCA14C");
.....
if(!auth.AddDataToHash(pbyHashBuffer, ulDataLen))
{
TRACE("CryptHashData function failed - Errorcode: %lu.\n", auth.GetAuthError());
}
You can't do it because it doesn't make any sense. CryptGetHashParam with the HP_HASHVAL option finalizes the hash, so there is no way to add data to it. If you want to "fork" the hash so that you can finalize it at some point as well as add data to it, you must duplicate the hash object prior to finalizing. Then you add the data to one of the hash objects and finalize the other. For example, you might do this if you wanted record a cumulative hash after every 1024 bytes of a data stream. You should not call CryptSetHashParam on the hash object that you are continuing to add data to.
CryptSetHashParam with the HP_HASHVAL option is a brutal hack to overcome a limitation in the CryptoAPI. The CryptoAPI will only sign a hash object, so if you want to sign some data that might have been hashed or generated outside of CAPI, you have to "jam" it into a hash object.
EDIT:
Based on your comment, I think you are looking for a way to serialize the hash object. I cannot find any evidence that CryptoAPI supports this. There are alternatives, however, that are basically variants of my "1024 bytes" example above. If you are hashing a sequence of files, you could simply compute and save the hash of each file. If you really need to boil it down to one value, then you can compute a modified hash where the first piece of data you hash for file i is the finalized hash for files 0, 1, 2, ..., i-1. So:
H-1 = empty,
Hi = MD5 (Hi-1 || filei)
As you go along, you can save the last successfully computed Hi value. In case of interruption, you can restart at file i+1. Note that, like any message digest, the above is completely sensitive to both order and content. This is something to consider on a dynamically changing file system. If files can be added or changed during the hashing operation, the meaning of the hash value will be affected. It might be rendered meaningless. You might want to be certain that both the sequence and content of the files you are hashing is frozen during the entire duration of the hash.