1346 lines
43 KiB
C++
1346 lines
43 KiB
C++
/*
|
|
Copyright (c) 2009 Microsoft Corporation
|
|
|
|
Module Name:
|
|
swriter.cpp
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "swriter.h"
|
|
|
|
|
|
#define __EVAL(X) X
|
|
#define __MERGE(A, B) A##B
|
|
#define __MAKE_WIDE(A) __MERGE(L, A)
|
|
#define __FUNCTION_WIDE__ __MAKE_WIDE(__EVAL(__FUNCTION__))
|
|
|
|
|
|
#define EXIT_ON_FAILURE(location) \
|
|
{ \
|
|
wprintf(L"(!) %s failed on the %s\n", __FUNCTION_WIDE__, location); \
|
|
goto _exit; \
|
|
} \
|
|
|
|
|
|
// GUID uniquely identifying the Writer
|
|
static const VSS_ID SampleWriterId =
|
|
{ 0x079462f2, 0x1079, 0x48dd, { 0xb3, 0xfb, 0xcc, 0xb2, 0xf2, 0x93, 0x4e, 0xc0 } };
|
|
|
|
// Name describing the Writer
|
|
static const WCHAR g_wszSampleWriterName[] = L"Sample Writer";
|
|
|
|
// Subdirectory where files will be put during restore
|
|
static const WCHAR g_wszAlternatePath[] = L"Restored";
|
|
|
|
// Registry path where user profiles are stored
|
|
const WCHAR g_wszProfileList[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList";
|
|
|
|
// Queue representing internal Writer state, its initialized-state flag and critical section
|
|
CQueue *g_queueRoot;
|
|
bool g_bQueueInitialized;
|
|
CRITICAL_SECTION g_cs;
|
|
|
|
//
|
|
// List of the components, their caption and corresponding wildcards
|
|
// For simplicity, Component field represents both component name as well as
|
|
// the directory in the user profile
|
|
// OnIdentify logic assumes that entries are sorted by the component name and
|
|
// terminating entry is filled with empty strings
|
|
//
|
|
SAMPLE_COMPONENT_TYPE g_sctAll[] =
|
|
{
|
|
{L"Documents", L"User Documents", L"*.docx"},
|
|
{L"Documents", L"User Documents", L"*.doc"},
|
|
{L"Documents", L"User Documents", L"*.xlsx"},
|
|
{L"Documents", L"User Documents", L"*.xls"},
|
|
{L"Pictures", L"User Images", L"*.jpeg"},
|
|
{L"Pictures", L"User Images", L"*.jpg"},
|
|
{L"Pictures", L"User Images", L"*.jpe"},
|
|
{L"Pictures", L"User Images", L"*.bmp"},
|
|
{L"Pictures", L"User Images", L"*.png"},
|
|
{L"Pictures", L"User Images", L"*.gif"},
|
|
{L"", L"", L""}
|
|
};
|
|
|
|
|
|
//
|
|
// Concatenate First and Second string placing Binder between them
|
|
// Result is allocated by the function on the heap
|
|
// If Result was pointing to a memory location, that memory will be released
|
|
//
|
|
bool ConcatenateWith(
|
|
PWSTR *pwszResult,
|
|
PCWSTR wszFirst,
|
|
PCWSTR wszSecond,
|
|
PCWSTR wszBinder
|
|
)
|
|
{
|
|
PWSTR wszResult = *pwszResult;
|
|
DWORD cchLength = 0;
|
|
bool bResult = false;
|
|
|
|
*pwszResult = NULL;
|
|
|
|
//
|
|
// Calculate length and allocate new string
|
|
//
|
|
cchLength = (DWORD)wcslen(wszFirst) + (DWORD)wcslen(wszSecond) + 2;
|
|
|
|
if (wszResult != NULL)
|
|
{
|
|
free(wszResult);
|
|
wszResult = NULL;
|
|
}
|
|
|
|
wszResult = (WCHAR *)malloc(cchLength * sizeof(WCHAR));
|
|
if (wszResult == NULL)
|
|
goto _exit;
|
|
|
|
// Merge strings
|
|
if (FAILED(StringCchPrintfW(wszResult, cchLength, L"%s%s%s", wszFirst, wszBinder, wszSecond)))
|
|
goto _exit;
|
|
|
|
// Delegate ownership of the new string
|
|
*pwszResult = wszResult;
|
|
wszResult = NULL;
|
|
bResult = true;
|
|
|
|
_exit:
|
|
if (wszResult != NULL)
|
|
{
|
|
free(wszResult);
|
|
wszResult = NULL;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//
|
|
// Concatenate First and Second string placing backslash between them
|
|
// Result is allocated by the function on the heap
|
|
// If Result was pointing to a memory location, that memory will be released
|
|
//
|
|
bool ConcatenateWithBackslash(
|
|
PWSTR *pwszResult,
|
|
PCWSTR wszFirst,
|
|
PCWSTR wszSecond
|
|
)
|
|
{
|
|
return ConcatenateWith(pwszResult, wszFirst, wszSecond, L"\\");
|
|
}
|
|
|
|
|
|
//
|
|
// Search for the files that match given wildcard inside a directory
|
|
// Directory to search in is a combination of Path and SubDirectory
|
|
// For simplicity function does recursive search that goes up to 3 levels deep
|
|
//
|
|
bool ValidateDirectory(
|
|
PCWSTR wszPath,
|
|
PCWSTR wszSubDirectory,
|
|
PCWSTR wszMask,
|
|
int iDepth = 0
|
|
)
|
|
{
|
|
WIN32_FIND_DATA sFindFileData = {0};
|
|
PWSTR wszDirectory = NULL;
|
|
PWSTR wszFiles = NULL;
|
|
PWSTR wszDirectories = NULL;
|
|
bool bResult = false;
|
|
HANDLE hFind = INVALID_HANDLE_VALUE;
|
|
|
|
// Go up to 3 levels in depth
|
|
if (iDepth == 3)
|
|
goto _exit;
|
|
|
|
// Pre-create all of the buffers
|
|
if (!ConcatenateWithBackslash(&wszDirectory, wszPath, wszSubDirectory))
|
|
EXIT_ON_FAILURE(L"first ConcatenateWithBackslash call");
|
|
if (!ConcatenateWithBackslash(&wszFiles, wszDirectory, wszMask))
|
|
EXIT_ON_FAILURE(L"second ConcatenateWithBackslash call");
|
|
if (!ConcatenateWithBackslash(&wszDirectories, wszDirectory, L"*"))
|
|
EXIT_ON_FAILURE(L"third ConcatenateWithBackslash call");
|
|
|
|
// Search for files matching the wildcard provided
|
|
hFind = FindFirstFileW(wszFiles, &sFindFileData);
|
|
if (INVALID_HANDLE_VALUE == hFind)
|
|
{
|
|
// go through the subdirectories if no files found
|
|
hFind = FindFirstFileW(wszDirectories, &sFindFileData);
|
|
if (INVALID_HANDLE_VALUE == hFind)
|
|
goto _exit;
|
|
do
|
|
{
|
|
// Discard current directory and parent directory
|
|
if ((wcscmp(sFindFileData.cFileName, L".") == 0) || (wcscmp(sFindFileData.cFileName, L"..") == 0))
|
|
continue;
|
|
|
|
// Discard files
|
|
if ((sFindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
|
|
continue;
|
|
|
|
//
|
|
// Discard reparse points
|
|
// This is a simple yet not fool proof method to avoid
|
|
// cycles that could be introduced by the reparse points
|
|
//
|
|
if ((sFindFileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0)
|
|
continue;
|
|
|
|
// Recursively search for the files in sub directories
|
|
if (ValidateDirectory(wszDirectory, sFindFileData.cFileName, wszMask, iDepth + 1))
|
|
{
|
|
bResult = true;
|
|
goto _exit;
|
|
}
|
|
}
|
|
while (FindNextFile(hFind, &sFindFileData) != 0);
|
|
// None of the directories had the file
|
|
}
|
|
else
|
|
{
|
|
// File(s) found, return with success
|
|
bResult = true;
|
|
}
|
|
|
|
_exit:
|
|
if (wszDirectory != NULL)
|
|
{
|
|
free(wszDirectory);
|
|
wszDirectory = NULL;
|
|
}
|
|
if (wszFiles != NULL)
|
|
{
|
|
free(wszFiles);
|
|
wszFiles = NULL;
|
|
}
|
|
if (wszDirectories != NULL)
|
|
{
|
|
free(wszDirectories);
|
|
wszDirectories = NULL;
|
|
}
|
|
if (INVALID_HANDLE_VALUE == hFind)
|
|
{
|
|
FindClose(hFind);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//
|
|
// Constructor
|
|
//
|
|
SampleWriter::SampleWriter()
|
|
{
|
|
wprintf(L" SampleWriter::SampleWriter called\n");
|
|
}
|
|
|
|
|
|
//
|
|
// Destructor
|
|
//
|
|
SampleWriter::~SampleWriter()
|
|
{
|
|
wprintf(L" SampleWriter::~SampleWriter called\n");
|
|
Uninitialize();
|
|
}
|
|
|
|
|
|
STDMETHODIMP SampleWriter::Uninitialize()
|
|
{
|
|
wprintf(L" SampleWriter::Uninitialize called\n");
|
|
|
|
CQueue *queue = NULL;
|
|
while (g_queueRoot != NULL)
|
|
{
|
|
queue = g_queueRoot->GetNext();
|
|
delete g_queueRoot;
|
|
g_queueRoot = queue;
|
|
}
|
|
queue = NULL;
|
|
|
|
DeleteCriticalSection(&g_cs);
|
|
|
|
return Unsubscribe();
|
|
}
|
|
|
|
|
|
//
|
|
// Writer initialization code
|
|
// This method controls Writer ID, name, usage type and source type
|
|
//
|
|
STDMETHODIMP SampleWriter::Initialize()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
wprintf(L" SampleWriter::Initialize called\n");
|
|
|
|
hr = CVssWriter::Initialize(
|
|
SampleWriterId, // ID
|
|
g_wszSampleWriterName, // name
|
|
VSS_UT_USERDATA, // usage type
|
|
VSS_ST_OTHER); // source type
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"(!) CVssWriter::Initialize failed\n");
|
|
return hr;
|
|
}
|
|
|
|
// Subscribe for events
|
|
hr = Subscribe();
|
|
if (FAILED(hr))
|
|
wprintf(L"(!) CVssWriter::Subscribe failed\n");
|
|
|
|
|
|
// Initialize the critical section preallocating necessary resources
|
|
if (!InitializeCriticalSectionAndSpinCount(&g_cs, 0x80000400))
|
|
{
|
|
wprintf(L"(!) InitializeCriticalSectionAndSpinCount failed\n");
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
//
|
|
// Set the initial values for the file group queue
|
|
// This simple Writer is expected to always return the same set
|
|
// of components and file groups
|
|
// In order to update its internal state it has to be restarted
|
|
// To make sure we create queue only once, g_bQueueInitialized flag
|
|
// is used
|
|
// This flag as well as the queue are protected by the critical section
|
|
//
|
|
g_queueRoot = NULL;
|
|
g_bQueueInitialized = false;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// Helper function creating user@domain string based on the SID provided
|
|
// Account name is allocated by the function on the heap
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::CreateAccountName(
|
|
PWSTR *pwszAccountName,
|
|
PSID pSid
|
|
)
|
|
{
|
|
SID_NAME_USE eSidNameUse = SidTypeUnknown;
|
|
DWORD cchUserNameLength = 0;
|
|
PWSTR wszUserName = NULL;
|
|
DWORD cchDomainLength = 0;
|
|
PWSTR wszDomain = NULL;
|
|
PWSTR wszAccount = NULL;
|
|
BOOL bResult = FALSE;
|
|
|
|
*pwszAccountName = NULL;
|
|
|
|
// First call should fail and just return the required buffer sizes
|
|
bResult = LookupAccountSidW(
|
|
NULL,
|
|
pSid,
|
|
NULL,
|
|
&cchUserNameLength,
|
|
NULL,
|
|
&cchDomainLength,
|
|
&eSidNameUse);
|
|
|
|
if (bResult != FALSE)
|
|
{
|
|
wprintf(L"(!) SampleWriter::CreateAccountName failed on the first LookupAccountSidW call\n");
|
|
goto _exit;
|
|
}
|
|
|
|
//
|
|
// Allocate buffers of a received size
|
|
//
|
|
wszUserName = (WCHAR *)malloc(cchUserNameLength * sizeof(WCHAR));
|
|
if (wszUserName == NULL)
|
|
{
|
|
wprintf(L"(!) SampleWriter::CreateAccountName failed on the first malloc call\n");
|
|
goto _exit;
|
|
}
|
|
|
|
wszDomain = (WCHAR *)malloc(cchDomainLength * sizeof(WCHAR));
|
|
if (wszDomain == NULL)
|
|
{
|
|
wprintf(L"(!) SampleWriter::CreateAccountName failed on the second malloc call\n");
|
|
goto _exit;
|
|
}
|
|
|
|
ZeroMemory(wszUserName, cchUserNameLength * sizeof(WCHAR));
|
|
ZeroMemory(wszDomain, cchDomainLength * sizeof(WCHAR));
|
|
|
|
// Look for the account and domain names
|
|
bResult = LookupAccountSidW(
|
|
NULL,
|
|
pSid,
|
|
wszUserName,
|
|
&cchUserNameLength,
|
|
wszDomain,
|
|
&cchDomainLength,
|
|
&eSidNameUse);
|
|
|
|
// Should not fail since buffers were of a right size
|
|
if (bResult == FALSE)
|
|
{
|
|
wprintf(L"(!) SampleWriter::CreateAccountName failed on the second LookupAccountSidW call\n");
|
|
goto _exit;
|
|
}
|
|
|
|
//
|
|
// We expect this SID to represent a regular user
|
|
// In other cases just silently skip - those are expected
|
|
//
|
|
if (eSidNameUse != SidTypeUser)
|
|
goto _exit;
|
|
|
|
// Create the user@domain string
|
|
if (!ConcatenateWith(&wszAccount, wszUserName, wszDomain, L"@"))
|
|
{
|
|
wprintf(L"(!) SampleWriter::CreateAccountName failed on the ConcatenateWith call\n");
|
|
goto _exit;
|
|
}
|
|
|
|
// Delegate ownership of the new string
|
|
*pwszAccountName = wszAccount;
|
|
wszAccount = NULL;
|
|
bResult = TRUE;
|
|
|
|
_exit:
|
|
if (wszUserName != NULL)
|
|
{
|
|
free(wszUserName);
|
|
wszUserName = NULL;
|
|
}
|
|
|
|
if (wszDomain != NULL)
|
|
{
|
|
free(wszDomain);
|
|
wszDomain = NULL;
|
|
}
|
|
|
|
if (wszAccount != NULL)
|
|
{
|
|
free(wszAccount);
|
|
wszAccount = NULL;
|
|
}
|
|
|
|
return (bResult != FALSE);
|
|
}
|
|
|
|
|
|
//
|
|
// Helper method that adds components representing user profile
|
|
// with all of its subcomponents
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::AddComponent(
|
|
IVssCreateWriterMetadata *pMetadata,
|
|
PCWSTR wszProfile,
|
|
PCWSTR wszProfilePath
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
PWSTR wszPath = NULL;
|
|
PWSTR wszAlternatePath = NULL;
|
|
PWSTR wszPreviousComponent = NULL;
|
|
PWSTR wszPathWithMask = NULL;
|
|
int i = 0;
|
|
bool bResult = false;
|
|
CQueue *queue = NULL;
|
|
|
|
// Add root component for this user profile
|
|
hr = pMetadata->AddComponent(
|
|
VSS_CT_FILEGROUP, // component type
|
|
NULL, // logical path
|
|
wszProfile, // component name
|
|
wszProfile, // user friendly caption
|
|
NULL,
|
|
0,
|
|
false,
|
|
false,
|
|
true, // selectable
|
|
true); // selectable for restore
|
|
|
|
// Cycle through all the data this Writer cares to protect
|
|
for (i = 0; SUCCEEDED(hr) && (wcscmp(g_sctAll[i].wszComponent, L"") != 0); ++i)
|
|
{
|
|
//
|
|
// This call will verify if files matching a certain wildcard exist
|
|
// in the directory wszProfilePath
|
|
// This logic exists to ensure that the Writer does not expose files
|
|
// Requester cannot backup (i.e. files that do not exist on the volume)
|
|
//
|
|
// There are several simplifications taking place here
|
|
// First: ValidateDirectory is not perfect and checks only up to
|
|
// three levels deep into the subdirectories; in reality entire tree
|
|
// should be validated
|
|
// Second: not only we skip component creation, we skip adding elements
|
|
// to the global queue
|
|
// This will have impact on the PostRestore as explained further
|
|
// in the appropriate event handler
|
|
//
|
|
// On the first note it may be worth mentioning that in most real life
|
|
// implementation Writer is tightly coupled with some application
|
|
// That application is most likely aware of all the existing as well as
|
|
// potential data stores on the machine
|
|
// In that case internal state may not exist at all and appropriate
|
|
// collections (components, file groups, etc.) will be polled directly
|
|
// from the application via interfaces Writer's author understands
|
|
//
|
|
if (!ValidateDirectory(wszProfilePath, g_sctAll[i].wszComponent, g_sctAll[i].wszFileGroupMask))
|
|
continue;
|
|
|
|
hr = S_OK;
|
|
|
|
// The wszPreviousComponent variable protects us from creating the same component twice
|
|
if ((wszPreviousComponent == NULL) || (wcscmp(wszPreviousComponent, g_sctAll[i].wszComponent) != 0))
|
|
{
|
|
//
|
|
// This will be a subcomponent to the previous root user profile one
|
|
// Please note it uses non-null logical path corresponding to the
|
|
// name of the parent component
|
|
//
|
|
hr = pMetadata->AddComponent(
|
|
VSS_CT_FILEGROUP, // component type
|
|
wszProfile, // logical path
|
|
g_sctAll[i].wszComponent, // component name
|
|
g_sctAll[i].wszComponentCaption, // user friendly caption
|
|
NULL,
|
|
0,
|
|
false,
|
|
false,
|
|
true, // selectable
|
|
true); // selectable for restore
|
|
|
|
//
|
|
// Preserve most recent component name if component creation succeeded
|
|
// and pre-create all the buffers required when adding files to the group
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
wszPreviousComponent = g_sctAll[i].wszComponent;
|
|
|
|
if (!ConcatenateWithBackslash(&wszPath, wszProfilePath, g_sctAll[i].wszComponent))
|
|
EXIT_ON_FAILURE(L"first ConcatenateWithBackslash call");
|
|
if (!ConcatenateWithBackslash(&wszAlternatePath, wszPath, g_wszAlternatePath))
|
|
EXIT_ON_FAILURE(L"second ConcatenateWithBackslash call");
|
|
}
|
|
}
|
|
|
|
// Add files to the file group and their alternate restore location
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// The set of files (represented by the wildcard) that we want to add
|
|
// should be grouped by the most recently added component
|
|
// To indicate that logical path and component name provided to this
|
|
// call should match path and name provided to the AddComponent call
|
|
//
|
|
hr = pMetadata->AddFilesToFileGroup(
|
|
wszProfile, // logical path
|
|
g_sctAll[i].wszComponent, // component name
|
|
wszPath, // directory containing files
|
|
g_sctAll[i].wszFileGroupMask, // files mask
|
|
true, // recursive
|
|
NULL);
|
|
|
|
// Make sure documents are not being overwritten during restore
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Critical section acquired in this thread so it is safe
|
|
// to look at the g_bQueueInitialized
|
|
// In more complex Writer case we may recreate the internal
|
|
// state instead of making sure we create it only once
|
|
// It may be important to keep copy of the previous state
|
|
// (or states) in more complex states
|
|
// In order to understand when and why, several things should
|
|
// be taken into account:
|
|
// - Writer can query session ID from any event w/ GetSessionId
|
|
// - Before the OnPrepareBackup call ID will be GUID_NULL and
|
|
// new ID won't be created until the OnPrepareSnapshot
|
|
// - Sequence from OnPrepareSnapshot to OnPostSnapshot is
|
|
// always being serialized (no concurrent snapshot sessions
|
|
// may exist)
|
|
//
|
|
if (g_bQueueInitialized == false)
|
|
{
|
|
//
|
|
// First create internal state describing file group
|
|
// that's being added to the component so it can be used
|
|
// during PostSnapshot and PostRestore
|
|
//
|
|
if (!ConcatenateWithBackslash(&wszPathWithMask, wszPath, g_sctAll[i].wszFileGroupMask))
|
|
EXIT_ON_FAILURE(L"third ConcatenateWithBackslash call");
|
|
queue = new CQueue(wszPathWithMask, wszProfile, g_sctAll[i].wszComponent);
|
|
if (queue == NULL)
|
|
EXIT_ON_FAILURE(L"CQueue operator new");
|
|
g_queueRoot = queue->Enqueue(g_queueRoot);
|
|
}
|
|
|
|
//
|
|
// This call is not bound to the component or file group
|
|
// and can be performed at any time during the OnIdentify
|
|
// It makes more sense to put it here in this case,
|
|
// but it's perfectly valid to call it once
|
|
// (e.g. to have one location where the entire
|
|
// directory structure is being restored to)
|
|
//
|
|
hr = pMetadata->AddAlternateLocationMapping(
|
|
wszPath, // directory containing files
|
|
g_sctAll[i].wszFileGroupMask, // files mask
|
|
true, // recursive
|
|
wszAlternatePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
bResult = SUCCEEDED(hr);
|
|
|
|
_exit:
|
|
// Don't release wszPreviousComponent - it's just a pointer!
|
|
if (wszPath != NULL)
|
|
{
|
|
free(wszPath);
|
|
wszPath = NULL;
|
|
}
|
|
if (wszPathWithMask != NULL)
|
|
{
|
|
free(wszPathWithMask);
|
|
wszPathWithMask = NULL;
|
|
}
|
|
if (wszAlternatePath != NULL)
|
|
{
|
|
free(wszAlternatePath);
|
|
wszAlternatePath = NULL;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//
|
|
// Helper method that generates data representing user profile in the system
|
|
// and passes it further so the appropriate components can be created
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::AddComponentForUserProfile(
|
|
IVssCreateWriterMetadata *pMetadata,
|
|
PCWSTR wszSid
|
|
)
|
|
{
|
|
PWSTR wszKeyName = NULL;
|
|
DWORD cchProfilePathLength = 100;
|
|
PWSTR wszProfilePath = NULL;
|
|
DWORD cchExpandedPathLength = 0;
|
|
PWSTR wszExpandedPath = NULL;
|
|
PWSTR wszAccount = NULL;
|
|
DWORD dwProfileFlags = 0;
|
|
DWORD dwSize = sizeof(DWORD); // initialized upfront to the dwProfileFlags size
|
|
HKEY hkeyProfile = NULL;
|
|
LONG lStatus = 0;
|
|
PSID pSid = NULL;
|
|
bool bResult = false;
|
|
|
|
if (!ConcatenateWithBackslash(&wszKeyName, g_wszProfileList, wszSid))
|
|
EXIT_ON_FAILURE(L"ConcatenateWithBackslash call");
|
|
|
|
if (ERROR_SUCCESS != RegOpenKeyEx(
|
|
HKEY_LOCAL_MACHINE,
|
|
wszKeyName,
|
|
0,
|
|
KEY_READ,
|
|
&hkeyProfile))
|
|
EXIT_ON_FAILURE(L"RegOpenKeyEx call");
|
|
|
|
lStatus = RegQueryValueExW(
|
|
hkeyProfile,
|
|
L"Flags",
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)&dwProfileFlags,
|
|
&dwSize);
|
|
|
|
// Skip atypical profiles and keys without flags value
|
|
if ((dwProfileFlags != 0) || (lStatus != ERROR_SUCCESS))
|
|
{
|
|
bResult = true;
|
|
goto _exit;
|
|
}
|
|
|
|
//
|
|
// Query for the user's local profile location
|
|
//
|
|
wszProfilePath = (WCHAR *)malloc(cchProfilePathLength * sizeof(WCHAR));
|
|
if (wszProfilePath == NULL)
|
|
EXIT_ON_FAILURE(L"first malloc call");
|
|
ZeroMemory(wszProfilePath, cchProfilePathLength * sizeof(WCHAR));
|
|
|
|
do
|
|
{
|
|
dwSize = (cchProfilePathLength - 1) * sizeof(WCHAR);
|
|
lStatus = RegQueryValueExW(
|
|
hkeyProfile,
|
|
L"ProfileImagePath",
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)wszProfilePath,
|
|
&dwSize);
|
|
|
|
// Resize the buffer to accommodate the data
|
|
if (lStatus == ERROR_MORE_DATA)
|
|
{
|
|
cchProfilePathLength *= 2;
|
|
wszProfilePath = (WCHAR *)realloc(wszProfilePath, cchProfilePathLength * sizeof(WCHAR));
|
|
if (wszProfilePath == NULL)
|
|
EXIT_ON_FAILURE(L"realloc call");
|
|
ZeroMemory(wszProfilePath, cchProfilePathLength * sizeof(WCHAR));
|
|
}
|
|
} while(lStatus == ERROR_MORE_DATA);
|
|
|
|
if (lStatus != ERROR_SUCCESS)
|
|
EXIT_ON_FAILURE(L"first RegQueryValueExW call");
|
|
|
|
//
|
|
// Expand environment variables in the profile path
|
|
//
|
|
cchExpandedPathLength = cchProfilePathLength + 100;
|
|
wszExpandedPath = (WCHAR *)malloc(cchExpandedPathLength * sizeof(WCHAR));
|
|
if (wszExpandedPath == NULL)
|
|
EXIT_ON_FAILURE(L"second malloc call");
|
|
ZeroMemory(wszExpandedPath, cchExpandedPathLength * sizeof(WCHAR));
|
|
|
|
dwSize = ExpandEnvironmentStringsW(wszProfilePath, wszExpandedPath, cchExpandedPathLength);
|
|
if (!dwSize || dwSize > cchExpandedPathLength)
|
|
EXIT_ON_FAILURE(L"ExpandEnvironmentStringsW call");
|
|
|
|
//
|
|
// Get SID for the account using this profile
|
|
//
|
|
if (ERROR_SUCCESS != RegQueryValueExW(
|
|
hkeyProfile,
|
|
L"Sid",
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&dwSize))
|
|
{
|
|
// Some keys have no Sid value - those are not interesting to us
|
|
bResult = true;
|
|
goto _exit;
|
|
}
|
|
|
|
pSid = LocalAlloc(LPTR, dwSize);
|
|
|
|
if (!pSid)
|
|
EXIT_ON_FAILURE(L"LocalAlloc call");
|
|
|
|
if (ERROR_SUCCESS != RegQueryValueExW(
|
|
hkeyProfile,
|
|
L"Sid",
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)pSid,
|
|
&dwSize))
|
|
EXIT_ON_FAILURE(L"third RegQueryValueExW call");
|
|
|
|
//
|
|
// Get the friendly account name
|
|
// Account name will contain both domain and user name
|
|
// Since it is prohibited for the component name to contain
|
|
// backslash, user@domain format will be used
|
|
//
|
|
if (!CreateAccountName(&wszAccount, pSid))
|
|
EXIT_ON_FAILURE(L"CreateAccountName call");
|
|
|
|
if (!AddComponent(pMetadata, wszAccount, wszExpandedPath))
|
|
EXIT_ON_FAILURE(L"AddComponent call");
|
|
|
|
bResult = true;
|
|
|
|
_exit:
|
|
if (wszKeyName != NULL)
|
|
{
|
|
free(wszKeyName);
|
|
wszKeyName = NULL;
|
|
}
|
|
|
|
if (wszProfilePath != NULL)
|
|
{
|
|
free(wszProfilePath);
|
|
wszProfilePath = NULL;
|
|
}
|
|
|
|
if (wszExpandedPath != NULL)
|
|
{
|
|
free(wszExpandedPath);
|
|
wszExpandedPath = NULL;
|
|
}
|
|
|
|
if (wszAccount != NULL)
|
|
{
|
|
free(wszAccount);
|
|
wszAccount = NULL;
|
|
}
|
|
|
|
if (hkeyProfile != NULL)
|
|
{
|
|
RegCloseKey(hkeyProfile);
|
|
hkeyProfile = NULL;
|
|
}
|
|
|
|
if (pSid != NULL)
|
|
{
|
|
LocalFree(pSid);
|
|
pSid = NULL;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//
|
|
// Helper method that generates the list of all the user profiles existing
|
|
// in the system and hand component generation over to another helper method
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::AddComponents(
|
|
IVssCreateWriterMetadata *pMetadata
|
|
)
|
|
{
|
|
DWORD cchKeyNameLength = 100;
|
|
PWSTR wszKeyName = NULL;
|
|
DWORD dwKeyIndex = 0;
|
|
HKEY hkeyProfiles = NULL;
|
|
LONG lStatus = 0;
|
|
bool bResult = false;
|
|
|
|
// Open the profile list
|
|
lStatus = RegOpenKeyExW(
|
|
HKEY_LOCAL_MACHINE,
|
|
g_wszProfileList,
|
|
0,
|
|
KEY_READ,
|
|
&hkeyProfiles);
|
|
|
|
if (lStatus != ERROR_SUCCESS)
|
|
EXIT_ON_FAILURE(L"RegOpenKeyExW call");
|
|
|
|
// Allocate the buffer for the key name
|
|
wszKeyName = (WCHAR *)malloc(cchKeyNameLength * sizeof(WCHAR));
|
|
if (wszKeyName == NULL)
|
|
EXIT_ON_FAILURE(L"malloc call");
|
|
ZeroMemory(wszKeyName, cchKeyNameLength * sizeof(WCHAR));
|
|
|
|
for (dwKeyIndex = 0; ; ++dwKeyIndex)
|
|
{
|
|
//
|
|
// Get the next key name
|
|
//
|
|
do
|
|
{
|
|
lStatus = RegEnumKeyW(hkeyProfiles, dwKeyIndex, wszKeyName, cchKeyNameLength);
|
|
|
|
// Resize the buffer to accommodate the data
|
|
if (lStatus == ERROR_MORE_DATA)
|
|
{
|
|
cchKeyNameLength *= 2;
|
|
wszKeyName = (WCHAR *)realloc(wszKeyName, cchKeyNameLength * sizeof(WCHAR));
|
|
if (wszKeyName == NULL)
|
|
EXIT_ON_FAILURE(L"realloc call");
|
|
ZeroMemory(wszKeyName, cchKeyNameLength * sizeof(WCHAR));
|
|
}
|
|
} while(lStatus == ERROR_MORE_DATA);
|
|
|
|
|
|
// Exit 'for' loop if done
|
|
if (lStatus == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
|
|
if (lStatus != ERROR_SUCCESS)
|
|
EXIT_ON_FAILURE(L"RegEnumKeyW call");
|
|
|
|
// Adding a profile component is best effort operation
|
|
if (!AddComponentForUserProfile(pMetadata, wszKeyName))
|
|
EXIT_ON_FAILURE(L"AddComponentForUserProfile call");
|
|
}
|
|
|
|
// Passed all the logic, success
|
|
bResult = true;
|
|
|
|
//
|
|
// Exit with cleanup
|
|
//
|
|
_exit:
|
|
if (wszKeyName != NULL)
|
|
{
|
|
free(wszKeyName);
|
|
wszKeyName = NULL;
|
|
}
|
|
if (hkeyProfiles != NULL)
|
|
{
|
|
RegCloseKey(hkeyProfiles);
|
|
hkeyProfiles = NULL;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//
|
|
// OnIdentify event handler
|
|
//
|
|
// It is called as a result of the Requester calling GatherWriterMetadata
|
|
// Writer should express its metadata by describing how Requesters should
|
|
// deal with it during backup and restore and which files it covers
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::OnIdentify(
|
|
IVssCreateWriterMetadata *pMetadata
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
bool bResult = false;
|
|
|
|
wprintf(L" SampleWriter::OnIdentify called\n");
|
|
|
|
//
|
|
// Set the restore method to restore to alternate location
|
|
// We will use AddAlternateLocationMapping method in the AddComponent
|
|
// to detail alternate restore locations
|
|
//
|
|
hr = pMetadata->SetRestoreMethod(
|
|
VSS_RME_RESTORE_TO_ALTERNATE_LOCATION,
|
|
NULL,
|
|
NULL,
|
|
VSS_WRE_ALWAYS,
|
|
false);
|
|
|
|
if (FAILED(hr))
|
|
EXIT_ON_FAILURE(L"SetRestoreMethod call");
|
|
|
|
// Protect the global queue and its flag with critical section
|
|
EnterCriticalSection(&g_cs);
|
|
|
|
//
|
|
// Add components and files that they cover
|
|
// Please note files for deletion may be added as well
|
|
// In that case it is expected that Writer will delete them from
|
|
// the snapshot during the OnPostSnapshot event
|
|
// It is important to make sure that no files are exposed twice
|
|
// by the same component, different components of the Writer or
|
|
// different Writers altogether
|
|
// Furthermore same files should not be exposed as part of
|
|
// the component and as files for deletion
|
|
// This is important when deciding on the wildcards for the file groups
|
|
//
|
|
if (!AddComponents(pMetadata))
|
|
EXIT_ON_FAILURE(L"AddComponents call");
|
|
|
|
//
|
|
// Returning false from OnIdentify has very strong implications
|
|
// Unlike other events it does not indicate error on the Requester side
|
|
// Instead it makes Writer completely invisible to the Requester
|
|
// For example if one executes vssadmin list Writers from the command line,
|
|
// this Writer will not be included in the list at all if we return false
|
|
//
|
|
// For code brevity this Writer does not register itself with the EventLog
|
|
// Production Writers however are obliged to report details of the failure
|
|
// in the EventLog if false is being returned from OnIdentify
|
|
// Despite the fact that Writer will become invisible to the Requesters,
|
|
// some Requesters may be tweaked to work with this specific Writer
|
|
// Those Requesters are expected to point users to the EvenLog for
|
|
// further details on the problem
|
|
//
|
|
g_bQueueInitialized = true;
|
|
LeaveCriticalSection(&g_cs);
|
|
bResult = true;
|
|
|
|
_exit:
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//
|
|
// OnPrepareBackup event handler
|
|
//
|
|
// This is called as a result of the Requester calling PrepareForBackup
|
|
// This indicates to the Writer that a backup sequence has started
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::OnPrepareBackup(
|
|
IVssWriterComponents *pComponents
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(pComponents);
|
|
wprintf(L" SampleWriter::OnPrepareBackup called\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// OnPrepareSnapshot event handler
|
|
//
|
|
// This is called as a result of the Requester calling DoSnapshotSet
|
|
// Time consuming actions required before I/O to the disk will be blocked
|
|
// prior to the snapshot should be performed here
|
|
// Please note that this event will be initiated by the VSS Service
|
|
// on behalf of the Requester
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::OnPrepareSnapshot()
|
|
{
|
|
wprintf(L" SampleWriter::OnPrepareSnapshot called\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// OnFreeze event handler
|
|
//
|
|
// This is called as a result of the Requester calling DoSnapshotSet
|
|
// At this stage Writer should communicate to its application that I/O
|
|
// on the disk will be held for a short period of time
|
|
// Writer is expected to handle this event quickly as it is essential for
|
|
// the system performance to get through the Freeze-Thaw sequence swiftly
|
|
// Please note that this event will be initiated by the VSS Service
|
|
// on behalf of the Requester
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::OnFreeze()
|
|
{
|
|
wprintf(L" SampleWriter::OnFreeze called\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// OnThaw event handler
|
|
//
|
|
// This is called as a result of the Requester calling DoSnapshotSet
|
|
// At this stage Writer should communicate to its application that I/O
|
|
// on the disk has been resumed
|
|
// Writer is expected to handle this event quickly
|
|
// Please note that this event will be initiated by the VSS Service
|
|
// on behalf of the Requester
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::OnThaw()
|
|
{
|
|
wprintf(L" SampleWriter::OnThaw called\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// OnPostSnapshot event handler
|
|
//
|
|
// This is called as a result of the Requester calling DoSnapshotSet
|
|
// At this stage Writer gets access to the shadow copy of the volumes that were
|
|
// snapshotted and is expected to perform auto recovery if needed
|
|
// Please note that this event will be initiated by the VSS Service
|
|
// on behalf of the Requester
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::OnPostSnapshot(
|
|
IVssWriterComponents *pComponents
|
|
)
|
|
{
|
|
UINT uiComponentCount = 0;
|
|
UINT uiCounter = 0;
|
|
HRESULT hr = S_OK;
|
|
bool bResult = false;
|
|
LONG lContext = 0;
|
|
|
|
wprintf(L" SampleWriter::OnPostSnapshot called\n");
|
|
|
|
lContext = GetContext();
|
|
|
|
// Perform auto recovery if possible (not blocked by the Requester)
|
|
if (lContext & VSS_VOLSNAP_ATTR_AUTORECOVER)
|
|
{
|
|
hr = pComponents->GetComponentCount(&uiComponentCount);
|
|
if (FAILED(hr))
|
|
EXIT_ON_FAILURE(L"GetComponentCount call");
|
|
|
|
//
|
|
// For the purpose of this sample we will only display the list
|
|
// of components that were backed up and files on the disk
|
|
// corresponding to those components
|
|
//
|
|
// In the real scenario Writer would most likely discover all
|
|
// of the files corresponding to the components that were
|
|
// backed up and if there is some post processing on them needed,
|
|
// appropriate actions would have been taken
|
|
// In case of documents and images from the snapshot there is nothing
|
|
// interesting we can do with them
|
|
//
|
|
// Writer has to match files on the source volume with files on
|
|
// the snapshot volume and perform auto-recovery on the snapshot,
|
|
// which at this point is available read/write
|
|
//
|
|
// To get to the volume name one should call GetVolumePathName
|
|
// on the files that were backed up, followed by the call to
|
|
// GetVolumeNameForVolumeMountPoint to get the original volume
|
|
// VSS API GetSnapshotDeviceName then finds the corresponding
|
|
// snapshot device name that should be used to get to the
|
|
// file system on the snapshot
|
|
//
|
|
|
|
for (uiCounter = 0; uiCounter < uiComponentCount; ++uiCounter)
|
|
{
|
|
CComPtr<IVssComponent> pComponent;
|
|
CComBSTR bstrComponentName;
|
|
CQueue *queue = NULL;
|
|
|
|
hr = pComponents->GetComponent(uiCounter, &pComponent);
|
|
if (FAILED(hr))
|
|
EXIT_ON_FAILURE(L"GetComponent call");
|
|
|
|
hr = pComponent->GetComponentName(&bstrComponentName);
|
|
if (FAILED(hr))
|
|
EXIT_ON_FAILURE(L"GetComponentName call");
|
|
|
|
// OLE2T should not be used in busy loops as it allocates
|
|
wprintf(L"(+) Component: %s\n", OLE2T(bstrComponentName));
|
|
|
|
//
|
|
// Search for all the matching components
|
|
//
|
|
// Our Writer state (queue) does not change during runtime and
|
|
// is global (spans across all the sessions i.e. is not session
|
|
// bound) so we would get away without using this lock
|
|
// For completeness however we'd like to highlight that
|
|
// one of the most important parts of the Writer is its ability
|
|
// to track the state between different events
|
|
//
|
|
EnterCriticalSection(&g_cs);
|
|
queue = g_queueRoot;
|
|
while (queue != NULL)
|
|
{
|
|
//
|
|
// When searching for the match it is important to remember
|
|
// that user potentially selected component with subcomponents
|
|
// In that case we have to match the value received from the
|
|
// GetComponentName call with the entire component path
|
|
//
|
|
|
|
// First let's see if there's a direct component name match
|
|
if (_wcsicmp(queue->GetComponentName(), OLE2T(bstrComponentName)) == 0)
|
|
{
|
|
wprintf(
|
|
L" (*) Files in this component (%s): %s\n",
|
|
queue->GetComponentName(),
|
|
queue->GetPath());
|
|
}
|
|
|
|
//
|
|
// Now let's see if we match one of the subcomponents
|
|
// Please note that this code assumes that only one level below
|
|
// the main component exists and simple path match is sufficient
|
|
// In the rare case of deeper component nesting, it may be
|
|
// required to parse the path looking for the '\' separators
|
|
//
|
|
if (_wcsicmp(queue->GetComponentPath(), OLE2T(bstrComponentName)) == 0)
|
|
{
|
|
wprintf(
|
|
L" (*) Files in subcomponent %s: %s\n",
|
|
queue->GetComponentPath(),
|
|
queue->GetPath());
|
|
}
|
|
|
|
queue = queue->GetNext();
|
|
}
|
|
LeaveCriticalSection(&g_cs);
|
|
}
|
|
}
|
|
|
|
bResult = true;
|
|
|
|
_exit:
|
|
if (!bResult)
|
|
{
|
|
// Set error
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//
|
|
// OnAbort event handler
|
|
//
|
|
// This function is called to abort the Writer's backup sequence
|
|
// This should only be called between OnPrepareBackup and OnPostSnapshot
|
|
// Writer may perform cleanup of some internal state it holds
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::OnAbort()
|
|
{
|
|
wprintf(L" SampleWriter::OnAbort called\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// OnBackupComplete event handler
|
|
//
|
|
// This function is called as a result of the Requester calling BackupComplete
|
|
// This is simply a notification for the Writer that backup is done
|
|
// and that it succeeded
|
|
// As a notification it is discouraged to raise errors from this event
|
|
// by returning false
|
|
//
|
|
STDMETHODIMP_(bool) SampleWriter::OnBackupComplete(
|
|
IVssWriterComponents *pComponents
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(pComponents);
|
|
wprintf(L" SampleWriter::OnBackupComplete called\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// OnBackupShutdown event handler
|
|
//
|
|
// This function is called at the end of the backup process, most likely after
|
|
// the OnBackupComplete call when Requester releases IVssBackupComponent
|
|
// This event may also happen as a result of the Requester shutting down
|
|
// or as a result of abnormal termination of the Requester
|
|
//
|
|
bool STDMETHODCALLTYPE SampleWriter::OnBackupShutdown(
|
|
VSS_ID id
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(id);
|
|
wprintf(L" SampleWriter::OnBackupShutdown called\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// OnPreRestore event handler
|
|
//
|
|
// This function is called as a result of the Requester calling PreRestore
|
|
// This will be called immediately before resting files
|
|
//
|
|
bool STDMETHODCALLTYPE SampleWriter::OnPreRestore(
|
|
IVssWriterComponents *pComponents
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(pComponents);
|
|
wprintf(L" SampleWriter::OnPreRestore called\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// OnPostRestore event handler
|
|
//
|
|
// This function is called as a result of the Requester calling PreRestore
|
|
// This will be called immediately after files being restored
|
|
//
|
|
bool STDMETHODCALLTYPE SampleWriter::OnPostRestore(
|
|
IVssWriterComponents *pComponents
|
|
)
|
|
{
|
|
UINT uiComponentCount = 0;
|
|
UINT uiCounter = 0;
|
|
HRESULT hr = S_OK;
|
|
bool bResult = false;
|
|
|
|
wprintf(L" SampleWriter::OnPostRestore called\n");
|
|
|
|
hr = pComponents->GetComponentCount(&uiComponentCount);
|
|
if (FAILED(hr))
|
|
EXIT_ON_FAILURE(L"GetComponentCount call");
|
|
|
|
//
|
|
// Similarily to the OnPostSnapshot event we will match components
|
|
// involved in this session with the previously created list of
|
|
// file groups
|
|
//
|
|
// As mentioned in the AddComponent this logic is a simplification
|
|
// of what real Writer would do
|
|
// This simplification comes from the fact that our restore depends
|
|
// on the queue constructed during the OnIdentify event
|
|
// This means that if data in the stores (Documens of Pictures
|
|
// within the user profile directory) has changed, restore may not
|
|
// be complete
|
|
// Alternatively we could depend on the g_sctAll variable
|
|
// However this has its limitations as well
|
|
// In real life Writer comes with the application and that application
|
|
// knows its existing as well as potential stores
|
|
// Our implementation knows only about the user profiles that
|
|
// exist in the registry now so e.g. restoring files that belong
|
|
// to the user who no longer has profile on this machine would be
|
|
// impossible
|
|
//
|
|
// So for the real implementation what you most likely want is
|
|
// the matching the components that were restored with your knowledge
|
|
// of what application can deal with
|
|
//
|
|
for (uiCounter = 0; uiCounter < uiComponentCount; ++uiCounter)
|
|
{
|
|
CComPtr<IVssComponent> pComponent;
|
|
CComBSTR bstrComponentName;
|
|
CQueue *queue = NULL;
|
|
|
|
hr = pComponents->GetComponent(uiCounter, &pComponent);
|
|
if (FAILED(hr))
|
|
EXIT_ON_FAILURE(L"GetComponent call");
|
|
|
|
hr = pComponent->GetComponentName(&bstrComponentName);
|
|
if (FAILED(hr))
|
|
EXIT_ON_FAILURE(L"GetComponentName call");
|
|
|
|
// OLE2T should not be used in busy loops as it allocates
|
|
wprintf(L"(+) Component: %s\n", OLE2T(bstrComponentName));
|
|
|
|
//
|
|
// Search for all the matching components
|
|
//
|
|
// Our Writer state (queue) does not change during runtime and
|
|
// is global (spans across all the sessions i.e. is not session
|
|
// bound) so we would get away without using this lock
|
|
// For completeness however we'd like to highlight that
|
|
// one of the most important parts of the Writer is its ability
|
|
// to track the state between different events
|
|
//
|
|
EnterCriticalSection(&g_cs);
|
|
queue = g_queueRoot;
|
|
while (queue != NULL)
|
|
{
|
|
//
|
|
// When searching for the match it is important to remember
|
|
// that user potentially selected component with subcomponents
|
|
// In that case we have to match the value received from the
|
|
// GetComponentName call with the entire component path
|
|
//
|
|
// To prevent Writer from overwriting files on your system
|
|
// no actual move will be performed
|
|
// In real life files would be moved from the alternate folder
|
|
// to the main location or in some ways merged, depending on
|
|
// the type of information they contain
|
|
//
|
|
if ((_wcsicmp(queue->GetComponentName(), OLE2T(bstrComponentName)) == 0) ||
|
|
(_wcsicmp(queue->GetComponentPath(), OLE2T(bstrComponentName)) == 0))
|
|
{
|
|
wprintf(
|
|
L" (*) Files in the directory: %s\\%s\n",
|
|
queue->GetPath(),
|
|
g_wszAlternatePath);
|
|
wprintf(
|
|
L" (*) Move to the directory: %s\n",
|
|
queue->GetPath());
|
|
}
|
|
|
|
queue = queue->GetNext();
|
|
}
|
|
LeaveCriticalSection(&g_cs);
|
|
}
|
|
|
|
bResult = true;
|
|
|
|
_exit:
|
|
if (!bResult)
|
|
{
|
|
// Set error
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
|