2025-11-28 00:35:46 +09:00

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;
}