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

2691 lines
86 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
/////////////////////////////////////////////////////////////////////
//
// The following backup/restore scenarios are addressed in the code
// 1. Optimized backup
// 2. Selective restore from optimized backup
// 3. Full volume restore from optimized backup
//
/////////////////////////////////////////////////////////////////////
#include <SDKDDKVer.h>
#include <stdio.h>
#include <tchar.h>
#include "ddpbackup.h"
#include <intsafe.h>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <atlbase.h>
#include <sal.h>
#include <wbemcli.h>
#include <comutil.h>
#include <wbemtime.h>
#include <winevt.h>
using namespace std;
// Backup/restore constants
const PCWSTR SYSTEM_VOLUME_INFORMATION = L"\\System Volume Information";
const PCWSTR DEDUP_FOLDER = L"\\Dedup";
const PCWSTR BACKUP_METADATA_FILE_NAME = L"dedupBackupMetadata.{741309a8-a42a-4830-b530-fad823933e6d}";
const PCWSTR BACKUP_METADATA_FORMAT = L"%s\r\n%s\r\n";
const PCWSTR LONG_PATH_PREFIX = L"\\\\?\\";
const PCWSTR DIRECTORY_BACKUP_FILE =
L"directoryBackup.{741309a8-a42a-4830-b530-fad823933e6d}";
const ULONG DEDUP_STORE_FOLDERS_COUNT = 3;
const PCWSTR DEDUP_STORE_FOLDERS[DEDUP_STORE_FOLDERS_COUNT] =
{
L"\\ChunkStore",
L"\\Settings",
L"\\State"
};
// WMI constants from Data Deduplication MOF schema
const PCWSTR CIM_V2_NAMESPACE = L"root\\cimv2";
const PCWSTR CIM_DEDUP_NAMESPACE = L"root\\Microsoft\\Windows\\Deduplication";
const PCWSTR CIM_DEDUP_CLASS_VOLUME = L"MSFT_DedupVolume";
const PCWSTR CIM_DEDUP_CLASS_VOLUME_METADATA = L"MSFT_DedupVolumeMetadata";
const PCWSTR CIM_DEDUP_CLASS_JOB = L"MSFT_DedupJob";
const PCWSTR CIM_DEDUP_METHOD_ENABLE = L"Enable";
const PCWSTR CIM_DEDUP_METHOD_DISABLE = L"Disable";
const PCWSTR CIM_DEDUP_METHOD_STOP = L"Stop";
const PCWSTR CIM_DEDUP_METHOD_START = L"Start";
const PCWSTR CIM_DEDUP_PROP_STOREID = L"StoreId";
const PCWSTR CIM_DEDUP_PROP_DATAACCESS = L"DataAccess";
const PCWSTR CIM_DEDUP_PROP_VOLUME = L"Volume";
const PCWSTR CIM_DEDUP_PROP_VOLUMEID = L"VolumeId";
const PCWSTR CIM_DEDUP_PROP_RETURNVALUE = L"ReturnValue";
const PCWSTR CIM_DEDUP_PROP_TYPE = L"Type";
const PCWSTR CIM_DEDUP_PROP_TIMESTAMP = L"Timestamp";
const PCWSTR CIM_DEDUP_PROP_WAIT = L"Wait";
const PCWSTR CIM_DEDUP_PROP_JOB = L"DedupJob";
const PCWSTR CIM_DEDUP_PROP_ID = L"Id";
const PCWSTR CIM_DEDUP_PROP_ERRORCODE = L"error_Code";
const PCWSTR CIM_DEDUP_PROP_ERRORMESSAGE = L"Message";
const PCWSTR CIM_DEDUP_PROP_PATH = L"__PATH";
const ULONG CIM_DEDUP_JOB_TYPE_UNOPT = 4;
const ULONG CIM_DEDUP_JOB_TYPE_GC = 2;
const PCWSTR DEDUP_OPERATIONAL_EVENT_CHANNEL_NAME =
L"Microsoft-Windows-Deduplication/Operational";
#define DDP_E_NOT_FOUND ((HRESULT)0x80565301L)
#define DDP_E_PATH_NOT_FOUND ((HRESULT)0x80565304L)
#define DDP_E_VOLUME_DEDUP_DISABLED ((HRESULT)0x80565323L)
#define ARRAY_LEN(A) (sizeof(A)/sizeof((A)[0]))
// Global variables
// Enums, classes
enum Action
{
BackupAction,
RestoreStubAction,
RestoreDataAction,
RestoreFileAction,
RestoreVolumeAction,
RestoreFilesAction
};
HRESULT RestoreVolume(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT ToggleDedupJobs(_In_ const wstring& volumeGuidName, _In_ bool enableJobs);
HRESULT ToggleDedupDataAccess(_In_ const wstring& volumeGuidName, _In_ bool enableDataAccess);
HRESULT CancelDedupJobs(_In_ const wstring& volumeGuidName);
HRESULT GetJobInstanceId(_In_ IWbemClassObject* startJobOutParams, _Out_ wstring& jobId);
HRESULT DisplayUnoptimizationFileError(_In_ EVT_HANDLE event);
HRESULT CheckForUnoptimizationFileErrors(_In_ const wstring& jobId, _Out_ bool& foundErrors);
HRESULT UnoptimizeSinceTimestamp(_In_ const wstring& volumeGuidName, _In_ wstring& backupTime);
HRESULT RunGarbageCollection(_In_ const wstring& volumeGuidName);
void DoBackup(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT RestoreStub(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT RestoreData(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT RestoreFilesData(_In_ const wstring& source, _In_ vector<wstring>& restoredFiles);
HRESULT BackupFile(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT BackupDirectory(_In_ const wstring& source, _In_ const wstring& destination);
void BackupDirectoryTree(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT RestoreFile(_In_ const wstring& source, _In_ const wstring& destination, _In_opt_ bool overWriteExisting = false);
HRESULT RestoreDirectory(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT RestoreDedupStoreDirectories(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT RestoreDedupStore(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT RestoreFiles(_In_ const wstring& source, _In_ const wstring& destination, _In_ const bool isVolumeRestore, _Out_opt_ vector<wstring>* pRestoredFiles);
HRESULT RestoreDirectoryTree(_In_ const wstring& source, _In_ const wstring& destination, _In_ const vector<wstring>& sourceExcludePaths, _Out_opt_ vector<wstring>* pRestoredFiles);
HRESULT DeleteDirectoryTree(_In_ const wstring& directory);
HRESULT DeleteDedupStore(_In_ const wstring& volume);
HRESULT GetDedupChunkStoreId(_In_ const wstring& volumeGuidName, _Out_ wstring& chunkStoreId);
HRESULT VolumeHasDedupMetadata(_In_ const wstring& volumeGuidName, _Out_ bool& hasDedupMetadata, _Out_ wstring& chunkStoreId);
void WriteBackupMetadata(_In_ const wstring& source, _In_ const wstring& destination);
HRESULT ReadBackupMetadata(_In_ const wstring& source, _Out_ wstring& chunkStoreId, _Out_ wstring& backupTime);
wstring BuildDedupStorePath(_In_ const wstring& volume);
HRESULT WmiGetWbemServices(_In_ PCWSTR wmiNamespace, _Out_ CComPtr<IWbemServices>& spWmi);
HRESULT WmiGetMethodInputParams(_In_ IWbemServices* pWmi, _In_ PCWSTR className, _In_ PCWSTR methodName, _Out_ CComPtr<IWbemClassObject>& spInParams);
HRESULT WmiAddVolumeInputParameter(_Inout_ IWbemClassObject* pParams, _In_ PCWSTR className, _In_ PCWSTR methodName, _In_ const wstring& volume);
HRESULT WmiAddInputParameter(_Inout_ IWbemClassObject* pParams, _In_ PCWSTR className, _In_ PCWSTR methodName, _In_ PCWSTR propertyName, _In_ variant_t& var);
HRESULT WmiGetErrorInfo(_Out_ HRESULT& hrOperation, _Out_ wstring& errorMessageOperation);
HRESULT WmiExecuteMethod(_In_ IWbemServices* pWmi, _In_ PCWSTR className, _In_ PCWSTR methodName, _In_opt_ IWbemClassObject* pInParams,
_Out_ CComPtr<IWbemClassObject>& spOutParams, _In_opt_ PCWSTR context = L"");
HRESULT WmiGetDedupInstanceByVolumeId(PCWSTR className, _In_ const wstring& volumeGuidName, _Out_ CComPtr<IWbemClassObject>& instance);
HRESULT WmiQueryDedupInstancesByVolumeId(_In_ IWbemServices* pWmi, _In_ const wstring& className, _In_ const wstring& volumeGuidName, _Out_ CComPtr<IEnumWbemClassObject>& instances);
void StringReplace(_Inout_ wstring& stringValue, _In_ const wstring& matchValue, _In_ const wstring& replaceValue);
wstring TrimTrailingSeparator(_In_ const wstring& str, _In_ WCHAR separator);
bool IsRootPath(_In_ const wstring& path);
void PrintUsage(_In_ LPCWSTR programName);
bool ParseCommandLine(_In_ int argc, _In_reads_(argc) _TCHAR* argv[], _Out_ Action *action, _Out_ wstring* source, _Out_ wstring* destination);
HRESULT ModifyPrivilege(_In_ LPCTSTR szPrivilege, _In_ BOOL fEnable);
HRESULT GetVolumeGuidNameForPath(_In_ const wstring& path, _Out_ wstring& volumeGuidName);
HRESULT GetEventData(_In_ EVT_HANDLE event, _In_ PCWSTR dataName, _Out_ variant_t& varData);
HRESULT GetFileSize(_In_ const std::wstring& FilePath, _Out_ LARGE_INTEGER& FileSize);
/////////////////////////////////////////////////////////////////////
//
// This class is for selective restore from optimized backup
//
/////////////////////////////////////////////////////////////////////
class CBackupStore: public IDedupReadFileCallback
{
private:
ULONGLONG m_refCount;
std::wstring m_backupLocation;
std::map<wstring, ULONGLONG> m_dataStreamMap;
public:
CBackupStore(_In_ const wstring& backupLocation)
{
m_refCount = 1;
m_backupLocation = backupLocation;
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ __RPC__in REFIID riid,
/* [iid_is][out] */ __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject)
{
if (IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, __uuidof(IDedupReadFileCallback)))
{
*ppvObject = this;
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
virtual ULONG STDMETHODCALLTYPE AddRef( void)
{
return (ULONG) InterlockedIncrement(&m_refCount);
}
virtual ULONG STDMETHODCALLTYPE Release( void)
{
ULONGLONG ref = InterlockedDecrement(&m_refCount);
if (ref == 0) delete this;
return (ULONG) ref;
}
virtual HRESULT STDMETHODCALLTYPE ReadBackupFile(
/* [in] */ __RPC__in BSTR FileFullPath,
/* [in] */ hyper FileOffset,
/* [in] */ ULONG SizeToRead,
/* [length_is][size_is][out] */ __RPC__out_ecount_part(SizeToRead, *ReturnedSize) BYTE *FileBuffer,
/* [out] */ __RPC__out ULONG *ReturnedSize,
/* [in] */ DWORD Flags)
{
// This method is called by the backup support COM object to read
// the backup database from the backup medium
UNREFERENCED_PARAMETER(Flags);
HRESULT hr = S_OK;
wstring filePath = m_backupLocation;
filePath += FileFullPath;
*ReturnedSize = 0;
// FileBuffer contents can be uninitialized after byte *ReturnedSize
HANDLE hFile = ::CreateFile(
filePath.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
wcout << L"Cannot open file " << filePath <<
L". Did we back it up? Error: " << GetLastError() << endl;
}
else
{
// This file was saved with BackupRead, so its contents are actually a series of
// WIN32_STREAM_ID structures that describe the original file
// To read data as we would read from the original file, we first need to find
// the DATA stream.
// If the file was not backed up with BackupRead you would not need this
LONGLONG dataStreamOffset = 0;
hr = FindDataStream(hFile, filePath, &dataStreamOffset);
if (SUCCEEDED(hr))
{
LONGLONG actualFileOffset = FileOffset + dataStreamOffset;
OVERLAPPED overlapped = {};
overlapped.Offset = LODWORD(actualFileOffset);
overlapped.OffsetHigh = HIDWORD(actualFileOffset);
if (!ReadFile(hFile, FileBuffer, SizeToRead, ReturnedSize, &overlapped))
{
wcout << L"Cannot read from file " << filePath <<
L". Did we back it up? gle= " << GetLastError() << endl;
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
CloseHandle(hFile);
}
return hr;
}
virtual HRESULT STDMETHODCALLTYPE OrderContainersRestore(
/* [in] */ ULONG NumberOfContainers,
/* [size_is][in] */ __RPC__in_ecount_full(NumberOfContainers) BSTR *ContainerPaths,
/* [out] */ __RPC__out ULONG *ReadPlanEntries,
/* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*ReadPlanEntries) DEDUP_CONTAINER_EXTENT **ReadPlan)
{
UNREFERENCED_PARAMETER(ContainerPaths);
// If you backed up to multiple tapes and parts of every file are split between the tapes
// you want avoid switching tapes back and forth as the restore engine the backup database files
// To implement this, you need to return the order in which the restore engine should read the files
// In this example, we tell the restore engine to read first 64k of every file first, then
// the remainder of the file. In a real tape backup application you would need to read the backup
// catalog, then return an array containing the file ranges on the first tape,
// then the files on the second tape, and so on
// If you are backing up on fixed disk you don't need to implement this, just uncomment the following 2
// lines and delete the rest:
//*ReadPlanEntries = 0;
//*ReadPlan = NULL;
if (ReadPlan == NULL)
{
*ReadPlanEntries = 0;
return S_OK;
}
ULONG fileFragments = 2 * NumberOfContainers;
*ReadPlan = (DEDUP_CONTAINER_EXTENT*)MIDL_user_allocate(fileFragments * sizeof(DEDUP_CONTAINER_EXTENT));
if (*ReadPlan != NULL)
{
*ReadPlanEntries = fileFragments;
for (ULONG i = 0; i < 2 * NumberOfContainers; i++)
{
(*ReadPlan)[i].ContainerIndex = i % NumberOfContainers;
wstring filePath = m_backupLocation;
wstring relativePath = ContainerPaths[i % NumberOfContainers];
filePath += relativePath;
// Chop the file at % of file size, to demonstrate extents
LARGE_INTEGER fileSize = {};
GetFileSize(filePath, fileSize);
ULONG extentBoundary = (fileSize.LowPart * 10) / 100;
if (i < NumberOfContainers)
{
(*ReadPlan)[i].StartOffset = 0;
(*ReadPlan)[i].Length = extentBoundary;
}
else
{
(*ReadPlan)[i].StartOffset = extentBoundary;
(*ReadPlan)[i].Length = LONGLONG_MAX; // this just says "until the end of the file"
}
}
}
else
{
// If allocation fails, we will just not optimize
*ReadPlanEntries = 0;
}
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE PreviewContainerRead(
/* [in] */ __RPC__in BSTR FileFullPath,
/* [in] */ ULONG NumberOfReads,
/* [size_is][in] */ __RPC__in_ecount_full(NumberOfReads) DDP_FILE_EXTENT *ReadOffsets)
{
UNREFERENCED_PARAMETER(FileFullPath);
UNREFERENCED_PARAMETER(NumberOfReads);
UNREFERENCED_PARAMETER(ReadOffsets);
// This will be called before the actual read, so you can optimize and do bigger reads instead
// of smaller ones. If you decide you want to do this, examine the ReadOffsets and do a bigger read
// into a big buffer, then satisfy the next reads from the buffer you allocated.
return S_OK;
}
private:
HRESULT FindDataStream(_In_ HANDLE hFile, _In_ const wstring& filePath, _Out_ LONGLONG* result)
{
// Cache the results per file path so we don't have to do this for every read
if (m_dataStreamMap.find(filePath) == m_dataStreamMap.end())
{
WIN32_STREAM_ID streamId = {};
DWORD bytesRead = 0;
while (streamId.dwStreamId != BACKUP_DATA)
{
::SetFilePointerEx(hFile, streamId.Size, NULL, FILE_CURRENT);
// The Size field is the actual size starting from the cStreamName field, so we only want to read the header
if (!ReadFile(hFile, &streamId, FIELD_OFFSET(WIN32_STREAM_ID, cStreamName), &bytesRead, NULL) ||
bytesRead != FIELD_OFFSET(WIN32_STREAM_ID, cStreamName))
{
wcout << "Cannot find the data stream in file. Did you use something other than BackupRead? Error: " <<
GetLastError() << endl;
return E_UNEXPECTED;
}
}
// Get the current position
LARGE_INTEGER zero = {};
LARGE_INTEGER currentPosition;
::SetFilePointerEx(hFile, zero, &currentPosition, FILE_CURRENT);
m_dataStreamMap[filePath] = currentPosition.QuadPart;
}
*result = m_dataStreamMap[filePath];
return S_OK;
}
};
/////////////////////////////////////////////////////////////////////
//
// Selective restore
//
/////////////////////////////////////////////////////////////////////
HRESULT RestoreStub(_In_ const wstring& source, _In_ const wstring& destination)
{
return RestoreFile(source, destination);
}
HRESULT RestoreData(_In_ const wstring& source, _In_ const wstring& destination)
{
// Source is a file name, but we need the directory name so we can read
// the backup database inside the backup store
size_t lastSeparator = source.rfind('\\');
if (lastSeparator == wstring::npos)
{
wcout << L"source is not a file path" << endl;
return E_UNEXPECTED;
}
wstring backupLocation = source.substr(0, lastSeparator);
CBackupStore* pStore = new(nothrow) CBackupStore(backupLocation);
if (pStore == NULL)
{
wcout << L"Not enough resources" << endl;
return E_OUTOFMEMORY;
}
CComPtr<IDedupBackupSupport> backupSupport;
HRESULT hr = backupSupport.CoCreateInstance(__uuidof(DedupBackupSupport), NULL);
if (FAILED(hr))
{
wcout << L"Cannot instantiate the restore engine, hr = 0x" << hex << hr << endl;
}
else
{
// NOTE: destination was already created by RestoreStub
BSTR bstrFile = SysAllocString(destination.c_str());
if (bstrFile == NULL)
{
wcout << L"Not enough resources" << endl;
delete pStore;
return E_OUTOFMEMORY;
}
hr = backupSupport->RestoreFiles(1, &bstrFile, pStore, DEDUP_RECONSTRUCT_UNOPTIMIZED, NULL);
SysFreeString(bstrFile);
}
if (FAILED(hr))
{
wcout << L"Restore failed, hr = 0x" << hex << hr << endl;
// We need to clean up on failure
DeleteFile(destination.c_str());
}
if (hr == S_FALSE)
{
wcout << L"Destination file is not a Data Deduplication file" << endl;
}
delete pStore;
return hr;
}
HRESULT RestoreFilesData(_In_ const wstring& source, _In_ vector<wstring>& restoredFiles)
{
HRESULT hr = S_OK;
size_t fileCount = restoredFiles.size();
if (fileCount == 0)
{
wcout << L"No files to restore" << endl;
return S_OK;
}
BSTR* bstrFiles = NULL;
CBackupStore* pStore = NULL;
HRESULT* hrRestoreResults = NULL;
CComPtr<IDedupBackupSupport> backupSupport;
bstrFiles = new BSTR[fileCount];
if (bstrFiles == NULL)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
ZeroMemory(bstrFiles, sizeof(BSTR*) * fileCount);
size_t index = 0;
for ( ; index < fileCount; ++index)
{
bstrFiles[index] = SysAllocString(restoredFiles[index].c_str());
if (bstrFiles[index] == NULL)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
}
hrRestoreResults = new HRESULT[fileCount];
if (hrRestoreResults == NULL)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
pStore = new(nothrow) CBackupStore(source);
if (pStore == NULL)
{
hr = E_OUTOFMEMORY;
goto Cleanup;
}
hr = backupSupport.CoCreateInstance(__uuidof(DedupBackupSupport), NULL);
if (FAILED(hr))
{
wcout << L"Cannot instantiate the restore engine, hr = 0x" << hex << hr << endl;
goto Cleanup;
}
// NOTE: destination stubs were already created by RestoreFiles
hr = backupSupport->RestoreFiles((ULONG)fileCount, bstrFiles, pStore, DEDUP_RECONSTRUCT_UNOPTIMIZED, hrRestoreResults);
if (FAILED(hr))
{
wcout << L"Restore failed, hr = 0x" << hex << hr << endl;
// Files not restored successfully will have deduplication reparse points
// Non-dedup files were completely restored when restoring stubs
// When error code is DDP_E_JOB_COMPLETED_PARTIAL_SUCCESS some deduplicated files were also fully restored
// Cleanup failed restores
for (index = 0; index < fileCount; ++index)
{
if (FAILED(hrRestoreResults[index]))
{
wcout << L"Failed to restore file " << bstrFiles[index] << L", hr = 0x" << hex << hr << endl;
DeleteFile(bstrFiles[index]);
}
}
}
Cleanup:
if (hr == E_OUTOFMEMORY)
{
wcout << L"Not enough resources" << endl;
}
if (pStore != NULL)
{
delete pStore;
}
if (hrRestoreResults != NULL)
{
delete [] hrRestoreResults;
}
if (bstrFiles != NULL)
{
for (index = 0; index < fileCount; ++index)
{
if (bstrFiles[index] != NULL)
{
SysFreeString(bstrFiles[index]);
}
}
delete [] bstrFiles;
}
return hr;
}
/////////////////////////////////////////////////////////////////////
//
// Backup full or selective volume
//
/////////////////////////////////////////////////////////////////////
void DoBackup(_In_ const wstring& source, _In_ const wstring& destination)
{
// Recursivelly back up the specified directory
BackupDirectoryTree(source, destination);
WCHAR volumePathName[MAX_PATH];
GetVolumePathName(source.c_str(), volumePathName, MAX_PATH);
if (source != volumePathName)
{
// Important: you must always ensure you back up the deduplication database, which is under
// System Volume Information\Dedup and contains the actual file data
// since we didn't back up the whole volume we need to back it up now
wstring dedupDatabase;
dedupDatabase = volumePathName;
dedupDatabase = TrimTrailingSeparator(dedupDatabase, L'\\');
dedupDatabase += SYSTEM_VOLUME_INFORMATION;
wstring databaseDestination = destination;
databaseDestination += SYSTEM_VOLUME_INFORMATION;
// Backup SVI folder
BackupDirectory(dedupDatabase, databaseDestination);
databaseDestination += DEDUP_FOLDER;
dedupDatabase += DEDUP_FOLDER;
// Backup the deduplication store
BackupDirectoryTree(dedupDatabase, databaseDestination);
}
WriteBackupMetadata(source, destination);
}
/////////////////////////////////////////////////////////////////////
//
// Full volume restore core
//
/////////////////////////////////////////////////////////////////////
HRESULT RestoreVolume(_In_ const wstring& source, _In_ const wstring& destination)
{
HRESULT hr = S_OK;
wstring destinationDedupStoreId;
wstring destinationVolumeGuidName;
wstring sourceDedupStoreId;
wstring backupTime;
bool destinationHasDedupMetadata = false;
wcout << L"Restoring files from '" << source << L"' to volume '" << destination << L"'" << endl;
hr = GetVolumeGuidNameForPath(destination, destinationVolumeGuidName);
// Get the chunk store ID and backup timestamp from the backup metadata
if (SUCCEEDED(hr))
{
hr = ReadBackupMetadata(source, sourceDedupStoreId, backupTime);
}
// Check for deduplication metadata
if (SUCCEEDED(hr))
{
hr = VolumeHasDedupMetadata(destinationVolumeGuidName, destinationHasDedupMetadata, destinationDedupStoreId);
}
if (SUCCEEDED(hr) && destinationHasDedupMetadata)
{
if (_wcsicmp(sourceDedupStoreId.c_str(), destinationDedupStoreId.c_str()) != 0)
{
wcout << L"Restore is unsupported. Source deduplication store ID '" << sourceDedupStoreId <<
L"' does not match destination ID '" << destinationDedupStoreId << L"'." << endl;
hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
}
if (SUCCEEDED(hr))
{
// Disable deduplication jobs on the volume to avoid disruption during restore
hr = ToggleDedupJobs(destinationVolumeGuidName, false);
}
// Cancel deduplication jobs running/queued on the volume
if (SUCCEEDED(hr))
{
hr = CancelDedupJobs(destinationVolumeGuidName);
}
// Unoptimize files that changed since backup timestamp
if (SUCCEEDED(hr))
{
hr = UnoptimizeSinceTimestamp(destinationVolumeGuidName, backupTime);
}
// Disable deduplication data access on the volume during restore
// NOTE: this operation causes the destination volume to dismount/remount
if (SUCCEEDED(hr))
{
hr = ToggleDedupDataAccess(destinationVolumeGuidName, false);
}
// Delete the deduplication store
if (SUCCEEDED(hr))
{
hr = DeleteDedupStore(destinationVolumeGuidName);
}
}
// Restore deduplication store
if (SUCCEEDED(hr))
{
hr = RestoreDedupStore(source, destination);
}
if (SUCCEEDED(hr) && !destinationHasDedupMetadata)
{
// Deduplication store is restored to a fresh volume
// Jobs and data access need to be disabled to avoid disruption during restore
// Disable deduplication jobs on the volume
hr = ToggleDedupJobs(destinationVolumeGuidName, false);
// Disable deduplication data access on the volume
// NOTE: this operation causes the destination volume to dismount/remount
if (SUCCEEDED(hr))
{
hr = ToggleDedupDataAccess(destinationVolumeGuidName, false);
}
// Set state such that deduplication is reenabled after restore
destinationHasDedupMetadata = true;
}
// Restore files on the volume
if (SUCCEEDED(hr))
{
// This sample restores files with source priority in cases where a file/directory
// exists in both the backup store and the target volume
// A real backup application might offer the option of target volume priority semantics
hr = RestoreFiles(source, destination, true, NULL);
}
// Reenable deduplication jobs and data access
if (SUCCEEDED(hr) && destinationHasDedupMetadata)
{
// Enable deduplication data access on the volume
// NOTE: this operation causes the destination volume to dismount/remount
hr = ToggleDedupDataAccess(destinationVolumeGuidName, true);
if (SUCCEEDED(hr))
{
// Enable deduplication jobs on the volume
hr = ToggleDedupJobs(destinationVolumeGuidName, true);
}
}
// Run GC job
if (SUCCEEDED(hr))
{
hr = RunGarbageCollection(destinationVolumeGuidName);
}
if (SUCCEEDED(hr))
{
wcout << L"Restore completed" << endl;
}
else
{
wcout << L"Restore completed with error 0x" << hex << hr << endl;
}
return hr;
}
HRESULT GetDedupChunkStoreId(_In_ const wstring& volumeGuidName, _Out_ wstring& chunkStoreId)
{
HRESULT hr = S_OK;
CComPtr<IWbemClassObject> spInstance;
chunkStoreId.clear();
// Returns S_FALSE if not found
hr = WmiGetDedupInstanceByVolumeId(CIM_DEDUP_CLASS_VOLUME_METADATA, volumeGuidName, spInstance);
if (hr == S_OK)
{
_variant_t var;
// Get the value of the StoreId property
hr = spInstance->Get(CIM_DEDUP_PROP_STOREID, 0, &var, 0, 0);
if (FAILED(hr))
{
wcout << L"IWbemClassObject::Get for property StoreId failed with error 0x" << hex << hr << endl;
}
else
{
chunkStoreId = var.bstrVal;
}
}
return hr;
}
HRESULT ToggleDedupJobs(_In_ const wstring& volumeGuidName, _In_ bool enableJobs)
{
wstring methodName = enableJobs ? CIM_DEDUP_METHOD_ENABLE : CIM_DEDUP_METHOD_DISABLE;
// Setup for WMI method call - get WBEM services, input parameter object
CComPtr<IWbemServices> spWmi;
HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi);
CComPtr<IWbemClassObject> spInParams;
if (SUCCEEDED(hr))
{
hr = WmiGetMethodInputParams(spWmi, CIM_DEDUP_CLASS_VOLUME, methodName.c_str(), spInParams);
}
if (SUCCEEDED(hr))
{
hr = WmiAddVolumeInputParameter(spInParams, CIM_DEDUP_CLASS_VOLUME, methodName.c_str(), volumeGuidName);
}
if (SUCCEEDED(hr))
{
CComPtr<IWbemClassObject> spOutParams;
hr = WmiExecuteMethod(spWmi, CIM_DEDUP_CLASS_VOLUME, methodName.c_str(), spInParams, spOutParams, L"Jobs");
}
return hr;
}
HRESULT ToggleDedupDataAccess(_In_ const wstring& volumeGuidName, _In_ bool enableDataAccess)
{
wstring methodName = enableDataAccess ? CIM_DEDUP_METHOD_ENABLE : CIM_DEDUP_METHOD_DISABLE;
// Setup for WMI method call - get WBEM services, input parameter object
CComPtr<IWbemServices> spWmi;
HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi);
CComPtr<IWbemClassObject> spInParams;
if (SUCCEEDED(hr))
{
hr = WmiGetMethodInputParams(spWmi, CIM_DEDUP_CLASS_VOLUME, methodName.c_str(), spInParams);
}
// Volume name parameter
if (SUCCEEDED(hr))
{
hr = WmiAddVolumeInputParameter(spInParams, CIM_DEDUP_CLASS_VOLUME, methodName.c_str(), volumeGuidName);
}
// DataAccess parameter
if (SUCCEEDED(hr))
{
variant_t dataAccessTrigger = true;;
hr = WmiAddInputParameter(spInParams, CIM_DEDUP_CLASS_VOLUME, methodName.c_str(), CIM_DEDUP_PROP_DATAACCESS, dataAccessTrigger);
}
// Execute method
if (SUCCEEDED(hr))
{
CComPtr<IWbemClassObject> spOutParams;
hr = WmiExecuteMethod(spWmi, CIM_DEDUP_CLASS_VOLUME, methodName.c_str(), spInParams, spOutParams, L"DataAccess");
}
return hr;
}
HRESULT CancelDedupJobs(_In_ const wstring& volumeGuidName)
{
CComPtr<IWbemServices> spWmi;
HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi);
// Get the job instance queued/running for the specified volume
CComPtr<IEnumWbemClassObject> spInstances;
if (SUCCEEDED(hr))
{
hr = WmiQueryDedupInstancesByVolumeId(spWmi, CIM_DEDUP_CLASS_JOB, volumeGuidName.c_str(), spInstances);
}
// Stop each job
while (SUCCEEDED(hr))
{
CComPtr<IWbemClassObject> spInstance;
ULONG uReturn = 0;
// Get a job instance
hr = spInstances->Next(WBEM_INFINITE, 1, &spInstance, &uReturn);
if (FAILED(hr))
{
wcout << L"IEnumWbemClassObject::Next failed with error 0x" << hex << hr << endl;
break;
}
if (uReturn == 0)
{
// All done
break;
}
// Get the __PATH property (used as input to ExecMethod)
variant_t varObjectPath;
hr = spInstance->Get(CIM_DEDUP_PROP_PATH, 0, &varObjectPath, 0, 0);
if (FAILED(hr))
{
wcout << L"IWbemClassObject::Get for property " << CIM_DEDUP_PROP_PATH << L" failed with error 0x" << hex << hr << endl;
break;
}
// Call the Stop method
if (SUCCEEDED(hr))
{
// NOTE: the MSFT_DedupJob.Stop method takes no input parameters
CComPtr<IWbemClassObject> spOutParams;
hr = WmiExecuteMethod(spWmi, varObjectPath.bstrVal, CIM_DEDUP_METHOD_STOP, NULL, spOutParams);
if (FAILED(hr))
{
break;
}
}
}
return hr;
}
HRESULT RestoreDedupStoreDirectories(_In_ const wstring& source, _In_ const wstring& destination)
{
wstring sourcePath = source;
wstring destinationPath = destination;
sourcePath += SYSTEM_VOLUME_INFORMATION;
destinationPath += SYSTEM_VOLUME_INFORMATION;
HRESULT hr = RestoreDirectory(sourcePath, destinationPath);
if (SUCCEEDED(hr) || hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS))
{
sourcePath += DEDUP_FOLDER;
destinationPath += DEDUP_FOLDER;
hr = RestoreDirectory(sourcePath, destinationPath);
}
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS))
{
hr = S_OK;
}
return hr;
}
HRESULT RestoreDedupStore(_In_ const wstring& source, _In_ const wstring& destination)
{
HRESULT hr = RestoreDedupStoreDirectories(source, destination);
if (SUCCEEDED(hr))
{
wstring sourceDedupStorePath = BuildDedupStorePath(source);
wstring destinationDedupStore = BuildDedupStorePath(destination);
for (int index = 0; SUCCEEDED(hr) && index < DEDUP_STORE_FOLDERS_COUNT; index++)
{
wstring sourceDedupPath = sourceDedupStorePath;
sourceDedupPath.append(DEDUP_STORE_FOLDERS[index]);
wstring destinationDedupPath = destinationDedupStore;
destinationDedupPath.append(DEDUP_STORE_FOLDERS[index]);
vector<wstring> excludePaths;
hr = RestoreDirectoryTree(sourceDedupPath, destinationDedupPath, excludePaths, NULL);
}
}
return hr;
}
HRESULT RestoreFiles(_In_ const wstring& source, _In_ const wstring& destination, _In_ const bool isVolumeRestore, _Out_opt_ vector<wstring>* pRestoredFiles)
{
HRESULT hr = S_OK;
// Restore files (exclude deduplication store)
vector<wstring> excludePaths;
excludePaths.push_back(BuildDedupStorePath(source));
if (SUCCEEDED(hr))
{
hr = RestoreDirectoryTree(source, destination, excludePaths, pRestoredFiles);
}
if (SUCCEEDED(hr) && !isVolumeRestore)
{
// NOTE: RestoreDirectoryTree also restores SVI folder
// If the backed up directory was a volume root there may be files to restore under SVI folder
// If the backed up directory was a folder then SVI should be cleaned up
// Directory deletion failures ignored intentionally
wstring dedupStorePath = TrimTrailingSeparator(destination, L'\\');
dedupStorePath.append(SYSTEM_VOLUME_INFORMATION);
RemoveDirectory(dedupStorePath.c_str());
}
return hr;
}
HRESULT GetJobInstanceId(_In_ IWbemClassObject* startJobOutParams, _Out_ wstring& jobId)
{
UNREFERENCED_PARAMETER(jobId);
_variant_t varArray;
jobId.clear();
// Get the value of the StoreId property
HRESULT hr = startJobOutParams->Get(CIM_DEDUP_PROP_JOB, 0, &varArray, 0, 0);
if (FAILED(hr))
{
wcout << L"IWbemClassObject::Get for property " << CIM_DEDUP_PROP_JOB << L" failed with error 0x" << hex << hr << endl;
}
if (varArray.vt != (VT_ARRAY | VT_UNKNOWN))
{
wcout << L"VT Type is unexpected for property " << CIM_DEDUP_PROP_JOB << endl;
hr = E_FAIL;
}
if (varArray.ppunkVal == NULL || *(varArray.ppunkVal) == NULL)
{
wcout << L"Property " << CIM_DEDUP_PROP_JOB << L" has a NULL value" << endl;
hr = E_FAIL;
}
IUnknown* unknown = NULL;
if (SUCCEEDED(hr))
{
long index = 0;
SAFEARRAY *sa = varArray.parray;
hr = SafeArrayGetElement(sa, &index, &unknown);
if (FAILED(hr))
{
wcout << L"SafeArrayGetElement failed with error 0x" << hex << hr << endl;
}
if (unknown == NULL)
{
wcout << L"Property " << CIM_DEDUP_PROP_JOB << L" has a NULL value" << endl;
hr = E_FAIL;
}
}
if (SUCCEEDED(hr))
{
CComPtr<IWbemClassObject> spJob;
CComPtr<IUnknown> spUnknown = unknown;
hr = spUnknown->QueryInterface(&spJob);
if (FAILED(hr))
{
wcout << L"IUnknown::QueryInterface for IWbemClassObject failed with error 0x" << hex << hr << endl;
}
if (SUCCEEDED(hr))
{
variant_t varJobId;
HRESULT hr = spJob->Get(CIM_DEDUP_PROP_ID, 0, &varJobId, 0, 0);
if (FAILED(hr))
{
wcout << L"IWbemClassObject::Get for property " << CIM_DEDUP_PROP_ID << L" failed with error 0x" << hex << hr << endl;
}
if (varJobId.vt != VT_BSTR)
{
wcout << L"VT Type is unexpected for property " << CIM_DEDUP_PROP_JOB << endl;
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
jobId = varJobId.bstrVal;
}
}
}
return hr;
}
HRESULT GetEventData(_In_ EVT_HANDLE event, _In_ PCWSTR dataName, _Out_ variant_t& varData)
{
HRESULT hr = S_OK;
wstring eventDataXPath(L"Event/EventData/Data[@Name='");
eventDataXPath.append(dataName);
eventDataXPath.append(L"']");
PCWSTR values[] = { eventDataXPath.c_str() };
EVT_HANDLE renderContext = EvtCreateRenderContext(
ARRAYSIZE(values),
values,
EvtRenderContextValues);
if (renderContext == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
wcout << L"EvtCreateRenderContext failed with error " << GetLastError() << endl;
}
if (SUCCEEDED(hr))
{
PEVT_VARIANT properties = NULL;
ULONG bufferUsed = 0;
ULONG propertyCount = 0;
BOOL result = EvtRender(
renderContext,
event,
EvtRenderEventValues,
0,
NULL,
&bufferUsed,
&propertyCount);
if (!result)
{
DWORD status = ::GetLastError();
if (status == ERROR_INSUFFICIENT_BUFFER)
{
WCHAR eventDataBuffer[1024] = {}; // production code should use dynamic memory
properties = (PEVT_VARIANT)eventDataBuffer;
result = ::EvtRender(
renderContext,
event,
EvtRenderEventValues,
sizeof(eventDataBuffer),
properties,
&bufferUsed,
&propertyCount);
}
}
if (!result)
{
hr = HRESULT_FROM_WIN32(GetLastError());
wcout << L"EvtRender failed with error " << GetLastError() << endl;
}
if (SUCCEEDED(hr))
{
if (properties[0].Type == EvtVarTypeString)
{
varData = properties[0].StringVal;
}
else if (properties[0].Type == EvtVarTypeUInt32)
{
varData.ulVal = properties[0].UInt32Val;
varData.vt = VT_UI4;
}
else
{
wcout << L"Conversion from " << properties[0].Type << L" not implemented" << endl;
hr = E_FAIL;
}
}
if (renderContext != NULL)
{
EvtClose(renderContext);
}
}
return hr;
}
HRESULT DisplayUnoptimizationFileError(_In_ EVT_HANDLE event)
{
wstring parentPath = L"";
wstring fileName = L"";
wstring errorMessage = L"";
ULONG errorCode = 0;
variant_t var;
HRESULT hr = GetEventData(event, L"ParentDirectoryPath", var);
if (SUCCEEDED(hr))
{
parentPath = var.bstrVal;
}
var.Clear();
hr = GetEventData(event, L"FileName", var);
if (SUCCEEDED(hr))
{
fileName = var.bstrVal;
}
var.Clear();
hr = GetEventData(event, L"ErrorMessage", var);
if (SUCCEEDED(hr))
{
errorMessage = var.bstrVal;
}
var.Clear();
hr = GetEventData(event, L"ErrorCode", var);
if (SUCCEEDED(hr))
{
errorCode = var.ulVal;
}
wstring filePath = parentPath + fileName;
wcout << L"Unoptimization file error" << endl
<< L" File path: " << filePath << endl
<< L" Error code: 0x" << hex << errorCode << endl
<< L" Error message: " << errorMessage << endl;
return hr;
}
HRESULT CheckForUnoptimizationFileErrors(_In_ const wstring& jobId, _Out_ bool& foundErrors)
{
HRESULT hr = S_OK;
foundErrors = false;
// Query for errors from the specified job ID
// Build the query string
const PCWSTR eventQueryPrefix =
L"<QueryList> <Query Id=\"0\" Path=\"Microsoft-Windows-Deduplication/Operational\"> "
L"<Select Path=\"Microsoft-Windows-Deduplication/Operational\">*[System[(EventID=6144)] "
L"and EventData[Data[(@Name=\"JobInstanceId\")]=\"";
const PCWSTR eventQuerySuffix =
L"\"]] </Select> </Query> </QueryList>";
wstring queryXml = eventQueryPrefix;
queryXml.append(jobId);
queryXml.append(eventQuerySuffix);
// Execute the query
EVT_HANDLE queryResult = EvtQuery(
NULL,
DEDUP_OPERATIONAL_EVENT_CHANNEL_NAME,
queryXml.c_str(),
//L"Event/System[EventID=6144]",
EvtQueryChannelPath | EvtQueryReverseDirection);
if (queryResult == NULL)
{
DWORD error = ::GetLastError();
hr = HRESULT_FROM_WIN32(error);
wcout << L"EvtQuery for unoptimization file errors failed with error " << error << endl;
}
// Process the returned events, if any
while (SUCCEEDED(hr))
{
DWORD eventsReturned = 0;
EVT_HANDLE eventHandle = NULL;
BOOL bStatus = EvtNext(
queryResult,
1,
&eventHandle,
INFINITE,
0,
&eventsReturned);
if (!bStatus)
{
DWORD error = ::GetLastError();
if (error != ERROR_NO_MORE_ITEMS)
{
hr = HRESULT_FROM_WIN32(error);
wcout << L"EvtNext for unoptimization file error failed with error " << error << endl;
}
else
{
break;
}
}
else if (eventsReturned == 1)
{
DisplayUnoptimizationFileError(eventHandle);
EvtClose(eventHandle);
foundErrors = true;
}
}
if (queryResult != NULL)
{
EvtClose(queryResult);
}
return hr;
}
HRESULT UnoptimizeSinceTimestamp(_In_ const wstring& volumeGuidName, _In_ wstring& backupTime)
{
// Setup for WMI method call - get WBEM services, input parameter object
CComPtr<IWbemServices> spWmi;
HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi);
CComPtr<IWbemClassObject> spInParams;
if (SUCCEEDED(hr))
{
hr = WmiGetMethodInputParams(spWmi, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, spInParams);
}
// Volume parameter
if (SUCCEEDED(hr))
{
hr = WmiAddVolumeInputParameter(spInParams, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, volumeGuidName);
}
// Job type parameter
if (SUCCEEDED(hr))
{
// Unoptimization job is type=4 (from Data Deduplication MOF schema)
variant_t var;
var.lVal = CIM_DEDUP_JOB_TYPE_UNOPT;
var.vt = VT_I4;
hr = WmiAddInputParameter(spInParams, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, CIM_DEDUP_PROP_TYPE, var);
}
// Timestamp parameter
if (SUCCEEDED(hr))
{
variant_t var = backupTime.c_str();
hr = WmiAddInputParameter(spInParams, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, CIM_DEDUP_PROP_TIMESTAMP, var);
}
// Job wait parameter for synchronous job
// NOTE: real backup applications might run optimization job asynchronously, track progress, etc.
if (SUCCEEDED(hr))
{
variant_t var = (bool) true;
hr = WmiAddInputParameter(spInParams, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, CIM_DEDUP_PROP_WAIT, var);
}
// Execute the unoptimization job
wstring jobId;
if (SUCCEEDED(hr))
{
CComPtr<IWbemClassObject> spOutParams;
hr = WmiExecuteMethod(spWmi, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, spInParams, spOutParams, L"Unoptimization");
// Get the job id for error tracking
if (SUCCEEDED(hr))
{
hr = GetJobInstanceId(spOutParams, jobId);
}
}
// Check for file errors
if (SUCCEEDED(hr))
{
bool foundErrors = false;
hr = CheckForUnoptimizationFileErrors(jobId, foundErrors);
if (foundErrors)
{
wcout << L"One or more files failed to unoptimize. Resolve the problems and retry the restore." << endl;
hr = E_FAIL;
}
}
return hr;
}
HRESULT RunGarbageCollection(_In_ const wstring& volumeGuidName)
{
// Setup for WMI method call - get WBEM services, input parameter object
CComPtr<IWbemServices> spWmi;
HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi);
CComPtr<IWbemClassObject> spInParams;
if (SUCCEEDED(hr))
{
hr = WmiGetMethodInputParams(spWmi, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, spInParams);
}
// Volume parameter
if (SUCCEEDED(hr))
{
hr = WmiAddVolumeInputParameter(spInParams, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, volumeGuidName);
}
// Job type parameter
if (SUCCEEDED(hr))
{
// GC job is type=2 (from Data Deduplication MOF schema)
variant_t var;
var.lVal = CIM_DEDUP_JOB_TYPE_GC;
var.vt = VT_I4;
hr = WmiAddInputParameter(spInParams, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, CIM_DEDUP_PROP_TYPE, var);
}
// Job wait parameter for synchronous job
// NOTE: Backup applications are not required to wait for GC completion
if (SUCCEEDED(hr))
{
variant_t var = (bool) true;
hr = WmiAddInputParameter(spInParams, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, CIM_DEDUP_PROP_WAIT, var);
}
// Execute the method
// Job will be queued and execute asynchronously
if (SUCCEEDED(hr))
{
CComPtr<IWbemClassObject> spOutParams;
hr = WmiExecuteMethod(spWmi, CIM_DEDUP_CLASS_JOB, CIM_DEDUP_METHOD_START, spInParams, spOutParams, L"GarbageCollection");
}
return hr;
}
HRESULT DeleteDedupStore(_In_ const wstring& volume)
{
HRESULT hr = S_OK;
wstring dedupStorePath = BuildDedupStorePath(volume);
hr = DeleteDirectoryTree(dedupStorePath);
return hr;
}
/////////////////////////////////////////////////////////////////////
//
// Program main
//
/////////////////////////////////////////////////////////////////////
int __cdecl _tmain(_In_ int argc, _In_reads_(argc) _TCHAR* argv[])
{
wstring source, destination;
Action action;
if (!ParseCommandLine(argc, argv, &action, &source, &destination))
{
PrintUsage(argv[0]);
return 1;
}
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
hr = CoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
if (SUCCEEDED(hr))
{
hr = ModifyPrivilege(SE_BACKUP_NAME, TRUE);
}
if (SUCCEEDED(hr))
{
hr = ModifyPrivilege(SE_RESTORE_NAME, TRUE);
}
// Permit paths longer than MAX_PATH
source = wstring(LONG_PATH_PREFIX) + source;
destination = wstring(LONG_PATH_PREFIX) + destination;
if (SUCCEEDED(hr))
{
switch (action)
{
case BackupAction:
DoBackup(source, destination);
break;
case RestoreStubAction:
hr = RestoreStub(source, destination);
break;
case RestoreDataAction:
hr = RestoreData(source, destination);
break;
case RestoreFileAction:
hr = RestoreStub(source, destination);
if (SUCCEEDED(hr))
{
hr = RestoreData(source, destination);
}
break;
case RestoreVolumeAction:
hr = RestoreVolume(source, destination);
break;
case RestoreFilesAction:
vector<wstring> restoredFiles;
hr = RestoreFiles(source, destination, false, &restoredFiles);
if (SUCCEEDED(hr))
{
hr = RestoreFilesData(source, restoredFiles);
}
restoredFiles.clear();
break;
}
}
CoUninitialize();
}
if (FAILED(hr)) return 1;
return 0;
}
/////////////////////////////////////////////////////////////////////
//
// Backup/restore utilities
//
/////////////////////////////////////////////////////////////////////
wstring BuildDedupStorePath(_In_ const wstring& volume)
{
wstring dedupStorePath = TrimTrailingSeparator(volume, L'\\');
dedupStorePath.append(SYSTEM_VOLUME_INFORMATION);
dedupStorePath.append(DEDUP_FOLDER);
return dedupStorePath;
}
/////////////////////////////////////////////////////////////////////
//
// Backup related methods
//
/////////////////////////////////////////////////////////////////////
HRESULT BackupFile(_In_ const wstring& source, _In_ const wstring& destination)
{
// Open the source file
HANDLE hSourceFile = ::CreateFile(
source.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hSourceFile == INVALID_HANDLE_VALUE)
{
wcout << L"CreateFile(" << source << L" failed with error " << GetLastError() << endl;
return HRESULT_FROM_WIN32(GetLastError());
}
// Open the backup medium
// in this example the medium is another file, but it could be tape, network server, etc...
HANDLE hDestinationFile = ::CreateFile(
destination.c_str(),
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hDestinationFile == INVALID_HANDLE_VALUE)
{
wcout << L"CreateFile(" << destination << L") failed with error " << GetLastError() << endl;
return HRESULT_FROM_WIN32(GetLastError());
}
// 4k is the default NTFS cluster size, and small enough to fit into the stack
// we could also readk 8k if we can afford the extra stack cost or allocate a heap buffer
// and read 64k or more
const DWORD DEFAULT_BUFFER_SIZE = 4086;
DWORD bytesRead = 0, bytesWritten = 0;
BYTE buffer[DEFAULT_BUFFER_SIZE];
LPVOID context = NULL;
HRESULT hr = S_OK;
// Read 4k at a time. BackupRead will return attributes, security, and reparse point information
while (BackupRead(hSourceFile, buffer, DEFAULT_BUFFER_SIZE, &bytesRead, FALSE, TRUE, &context) && bytesRead > 0)
{
// Save the data describing the source file to the destination medium.
// we do a write file here, but if this would be a network server you could send on a socket
if (!WriteFile(hDestinationFile, buffer, bytesRead, &bytesWritten, NULL))
{
wcout << L"WriteFile(" << destination << L") failed with error " << GetLastError() << endl;
hr = HRESULT_FROM_WIN32(GetLastError());
break;
}
if (bytesRead != bytesWritten)
{
wcout << L"WriteFile(" << destination << L") unexpectedly wrote less bytes than expected (expected:" << bytesRead << L" written:" << bytesWritten << L")" << endl;
hr = E_UNEXPECTED;
break;
}
}
// Call BackupRead one more time to clean up the context
BackupRead(hSourceFile, NULL, 0, NULL, TRUE, TRUE, &context);
// Close the source file
CloseHandle(hSourceFile);
// Close the backup medium
CloseHandle(hDestinationFile);
return hr;
}
HRESULT BackupDirectory(_In_ const wstring& source, _In_ const wstring& destination)
{
HRESULT hr = S_OK;
// Create the corresponding directory in the destination
if (!::CreateDirectory(destination.c_str(), NULL))
{
DWORD error = GetLastError();
if (error != ERROR_ALREADY_EXISTS)
{
wcout << L"CreateDirectory(" << destination << L") failed with error " << GetLastError() << endl;
hr = HRESULT_FROM_WIN32(error);
}
}
if (SUCCEEDED(hr))
{
// Backup the directory
wstring directoryBackupPath = destination;
directoryBackupPath.append(L"\\");
directoryBackupPath.append(DIRECTORY_BACKUP_FILE);
hr = BackupFile(source, directoryBackupPath);
}
return hr;
}
void BackupDirectoryTree(_In_ const wstring& source, _In_ const wstring& destination)
{
// Backup the directory
HRESULT hr = BackupDirectory(source, destination);
if (FAILED(hr))
{
return;
}
// Walk through all the files and subdirectories
WIN32_FIND_DATA findData;
wstring pattern = source;
pattern += L"\\*";
HANDLE hFind = FindFirstFile(pattern.c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
// If not . or ..
if (findData.cFileName[0] != '.')
{
wstring newSource = source;
newSource += '\\';
newSource += findData.cFileName;
wstring newDestination = destination;
newDestination += '\\';
newDestination += findData.cFileName;
// Backup the source file or directory
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// NOTE: This code is using recursion for simplicity, however a real backup application
// should avoid recursion since it will overflow the stack for a deep directory tree
BackupDirectoryTree(newSource, newDestination);
}
else
{
// Do BackupRead and backup the file
hr = BackupFile(newSource, newDestination);
if (FAILED(hr))
{
// NOTE: This code ignores BackupFile errors,
// but a backup app might handle it differently
wcout << L"BackupFile failed, hr = 0x" << hex << hr << endl;
}
}
}
} while (FindNextFile(hFind, &findData));
FindClose(hFind);
}
}
HRESULT VolumeHasDedupMetadata(_In_ const wstring& volumeGuidName, _Out_ bool& hasDedupMetadata, _Out_ wstring& chunkStoreId)
{
chunkStoreId.clear();
hasDedupMetadata = false;
HRESULT hr = GetDedupChunkStoreId(volumeGuidName, chunkStoreId);
if (SUCCEEDED(hr) && !chunkStoreId.empty())
{
hasDedupMetadata = true;
}
return hr;
}
void WriteBackupMetadata(_In_ const wstring& source, _In_ const wstring& destination)
{
wstring volumeGuidName;
wstring chunkStoreId;
HRESULT hr = GetVolumeGuidNameForPath(source, volumeGuidName);
// Get the deduplication chunk store ID
if (SUCCEEDED(hr))
{
hr = GetDedupChunkStoreId(volumeGuidName.c_str(), chunkStoreId);
}
// Write the store ID and backup timestamp to the backup metadata file
if (SUCCEEDED(hr) && !chunkStoreId.empty())
{
wstring filePath = destination;
filePath.append(L"\\");
filePath.append(BACKUP_METADATA_FILE_NAME);
FILE* metadataFile = NULL;
errno_t err = _wfopen_s(&metadataFile, filePath.c_str(), L"w");
if (err != 0)
{
wcout << L"Unable to create backup metadata file: " << filePath << endl;
}
else
{
// A real backup application would use the VSS snapshot timestamp
FILETIME ftNow = {};
GetSystemTimeAsFileTime(&ftNow);
// Convert time to wbem time string
WBEMTime backupTime(ftNow);
bstr_t bstrTime = backupTime.GetDMTF();
_ftprintf(metadataFile, BACKUP_METADATA_FORMAT, chunkStoreId.c_str(), (WCHAR*)bstrTime);
fclose(metadataFile);
}
}
}
HRESULT ReadBackupMetadata(_In_ const wstring& source, _Out_ wstring& chunkStoreId, _Out_ wstring& backupTime)
{
HRESULT hr = S_OK;
wstring filePath = source;
backupTime.clear();
chunkStoreId.clear();
filePath.append(L"\\");
filePath.append(BACKUP_METADATA_FILE_NAME);
FILE* metadataFile = NULL;
errno_t err = _wfopen_s(&metadataFile, filePath.c_str(), L"r");
if (err != 0)
{
wcout << L"Unable to open backup metadata file: " << filePath << endl;
hr = E_FAIL;
}
else
{
WCHAR storeId[] = L"{00000000-0000-0000-0000-000000000000}";
WCHAR timestamp[] = L"yyyymmddhhmmss.nnnnnn-ggg";
int converted = fwscanf_s(metadataFile, BACKUP_METADATA_FORMAT, storeId, ARRAY_LEN(storeId), timestamp, ARRAY_LEN(timestamp));
if (converted != 2)
{
wcout << L"Unable to read chunk store ID and backup time from backup metadata file: " << filePath << endl;
}
else
{
chunkStoreId = storeId;
backupTime = timestamp;
}
fclose(metadataFile);
}
return hr;
}
/////////////////////////////////////////////////////////////////////
//
// Restore related methods
//
/////////////////////////////////////////////////////////////////////
HRESULT RestoreFile(_In_ const wstring& source, _In_ const wstring& destination, _In_opt_ bool overWriteExisting)
{
// Create destination dir if it doesn't exist
size_t lastSeparator = destination.rfind('\\');
if (lastSeparator == wstring::npos)
{
wcout << L"destination is not a file path" << endl;
return E_UNEXPECTED;
}
wstring destinationLocation = destination.substr(0, lastSeparator);
::CreateDirectory(destinationLocation.c_str(), NULL);
// Open the backup medium
// In this example the medium is another file, but it could be tape, network server, etc...
HANDLE hSourceFile = ::CreateFile(
source.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hSourceFile == INVALID_HANDLE_VALUE)
{
wcout << L"CreateFile(" << source << L") failed with error " << GetLastError() << endl;
return HRESULT_FROM_WIN32(GetLastError());
}
// Open the file to be restored
HANDLE hDestinationFile = ::CreateFile(
destination.c_str(),
GENERIC_WRITE | WRITE_OWNER | WRITE_DAC,
FILE_SHARE_READ,
NULL,
overWriteExisting ? OPEN_EXISTING : CREATE_ALWAYS,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hDestinationFile == INVALID_HANDLE_VALUE)
{
wcout << L"CreateFile(" << destination << L") failed with error " << GetLastError() << endl;
return HRESULT_FROM_WIN32(GetLastError());
}
const DWORD DEFAULT_BUFFER_SIZE = 4086;
DWORD bytesRead, bytesWritten;
BYTE buffer[DEFAULT_BUFFER_SIZE];
LPVOID context = NULL;
HRESULT hr = S_OK;
// Read 4k at a time from the backup medium. BackupRead will return attributes, security, and reparse point information
while (ReadFile(hSourceFile, buffer, DEFAULT_BUFFER_SIZE, &bytesRead, NULL) && bytesRead > 0)
{
// Call BackupWrite to restore the file, including security, attributes and reparse point
if (!BackupWrite(hDestinationFile, buffer, bytesRead, &bytesWritten, FALSE, TRUE, &context))
{
wcout << L"BackupWrite(" << destination << L") failed with error " << GetLastError() << endl;
hr = HRESULT_FROM_WIN32(GetLastError());
break;
}
if (bytesRead != bytesWritten)
{
wcout << L"BackupWrite(" << destination << L") unexpectedly wrote less bytes than expected (expected:" << bytesRead << L" written:" << bytesWritten << L")" << endl;
hr = E_UNEXPECTED;
break;
}
}
// Call BackupWrite one more time to clean up the context
BackupWrite(hDestinationFile, NULL, 0, NULL, TRUE, TRUE, &context);
// Close the backup medium
CloseHandle(hSourceFile);
// Close the destination file
CloseHandle(hDestinationFile);
return hr;
}
HRESULT RestoreDirectory(_In_ const wstring& source, _In_ const wstring& destination)
{
HRESULT hr = S_OK;
wstring directorySourcePath = source;
directorySourcePath.append(L"\\");
directorySourcePath.append(DIRECTORY_BACKUP_FILE);
// Create the corresponding directory in the destination, if not already present
if (!::CreateDirectory(destination.c_str(), NULL))
{
DWORD error = GetLastError();
// Access denied error can occur for volume root directory
// The sample code also exempts access denied error for all other directories
// A real backup application may handle this condition differently
if (error != ERROR_ALREADY_EXISTS && error != ERROR_ACCESS_DENIED)
{
wcout << L"CreateDirectory(" << destination << L") failed with error " << GetLastError() << endl;
hr = HRESULT_FROM_WIN32(error);
}
}
if (SUCCEEDED(hr))
{
// Restore the directory
RestoreFile(directorySourcePath, destination, true);
}
return hr;
}
HRESULT DeleteDirectoryTree(_In_ const wstring& directory)
{
HRESULT hr = S_OK;
// Walk through all the files and subdirectories
WIN32_FIND_DATA findData;
wstring pattern = directory;
pattern += L"\\*";
HANDLE hFind = FindFirstFile(pattern.c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
// If not . or ..
if (findData.cFileName[0] != '.')
{
wstring newPath = directory;
newPath += '\\';
newPath += findData.cFileName;
// Backup the source file or directory
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// NOTE: This code is using recursion for simplicity, however a real backup application
// should avoid recursion since it will overflow the stack for a deep directory tree
hr = DeleteDirectoryTree(newPath);
if (FAILED(hr))
{
break;
}
// NOTE: This code is ignoring directory deletion failures since the same tree will be restored anyway.
// real backup applications migh choose to handle this differently.
RemoveDirectory(newPath.c_str());
}
else
{
// Do BackupRead and backup the file
BOOL result = DeleteFile(newPath.c_str());
if (!result)
{
hr = HRESULT_FROM_WIN32(GetLastError());
wcout << L"DeleteFile failed, hr = 0x" << hex << hr << endl;
break;
}
}
}
} while (FindNextFile(hFind, &findData));
FindClose(hFind);
}
return hr;
}
HRESULT RestoreDirectoryTree(_In_ const wstring& source, _In_ const wstring& destination, _In_ const vector<wstring>& sourceExcludePaths, _Out_opt_ vector<wstring>* pRestoredFiles)
{
HRESULT hr = S_OK;
if (pRestoredFiles != NULL)
{
pRestoredFiles->clear();
}
// Check for exclusion
for (size_t index = 0; index < sourceExcludePaths.size(); index++)
{
wstring excludePath = sourceExcludePaths[index];
if (wcscmp(source.c_str(), excludePath.c_str()) == 0)
{
return hr = S_FALSE;
}
}
// Restore the directory
hr = RestoreDirectory(source, destination);
if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) && hr != HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED))
{
return hr;
}
hr = S_OK;
wstring trimmedSource = TrimTrailingSeparator(source, L'\\');
// Walk through all the files and subdirectories
WIN32_FIND_DATA findData;
wstring pattern = trimmedSource;
pattern += L"\\*";
HANDLE hFind = FindFirstFile(pattern.c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
// If not . or ..
if (findData.cFileName[0] != '.')
{
wstring newSource = trimmedSource;
newSource += '\\';
newSource += findData.cFileName;
wstring newDestination = destination;
newDestination += '\\';
newDestination += findData.cFileName;
// Restore the file or directory
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// NOTE: This code is using recursion for simplicity, however a real backup application
// should avoid recursion since it will overflow the stack for a deep directory tree
hr = RestoreDirectoryTree(newSource, newDestination, sourceExcludePaths, pRestoredFiles);
if (FAILED(hr))
{
break;
}
}
else if ((_wcsicmp(findData.cFileName, DIRECTORY_BACKUP_FILE) == 0) ||
(_wcsicmp(findData.cFileName, BACKUP_METADATA_FILE_NAME) == 0))
{
// This file is backup metadata, not original volume data
continue;
}
else
{
// Restore the file
hr = RestoreFile(newSource, newDestination);
if (FAILED(hr))
{
if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) || hr == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION))
{
// Some files may be in busy, protected by a filter, etc.
// An online restore may not be able to replace every file
wcout << L"Warning: continuing restore after RestoreFile failed, hr = 0x" << hex << hr << endl;
hr = S_OK;
}
else
{
wcout << L"RestoreFile failed, hr = 0x" << hex << hr << endl;
break;
}
}
if (pRestoredFiles)
{
pRestoredFiles->push_back(newDestination);
}
}
}
} while (FindNextFile(hFind, &findData));
FindClose(hFind);
}
return hr;
}
/////////////////////////////////////////////////////////////////////
//
// WMI utilities
//
/////////////////////////////////////////////////////////////////////
HRESULT WmiGetWbemServices(_In_ PCWSTR wmiNamespace, _Out_ CComPtr<IWbemServices>& spWmi)
{
CComPtr<IWbemLocator> spLocator = NULL;
bstr_t bstrNamespace = wmiNamespace;
HRESULT hr = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator,
(void**) &spLocator
);
if (FAILED(hr))
{
wcout << L"CoCreateInstance(IWbemLocator) failed, hr = 0x" << hex << hr << endl;
}
if (SUCCEEDED(hr))
{
hr = spLocator->ConnectServer(
bstrNamespace,
NULL,
NULL,
NULL,
0L,
NULL,
NULL,
&spWmi
);
if (FAILED(hr))
{
wcout << L"unable to connect to WMI; namespace " << CIM_DEDUP_NAMESPACE << L", hr = 0x" << hex << hr << endl;
}
}
if (SUCCEEDED(hr))
{
hr = CoSetProxyBlanket(
spWmi,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
NULL,
RPC_C_AUTHN_LEVEL_PKT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE
);
if (FAILED(hr))
{
wcout << L"CoSetProxyBlanket failed, hr = 0x" << hex << hr << endl;
}
}
return hr;
}
HRESULT WmiGetMethodInputParams(_In_ IWbemServices* pWmi, _In_ PCWSTR className, _In_ PCWSTR methodName, _Out_ CComPtr<IWbemClassObject>& spInParams)
{
CComPtr<IWbemClassObject> spClass;
spInParams = NULL;
HRESULT hr = pWmi->GetObject(bstr_t(className), 0, NULL, &spClass, NULL);
if (FAILED(hr))
{
wcout << L"WMI query for class " << className << L" failed with error 0x" << hex << hr << endl;
}
CComPtr<IWbemClassObject> spInParamsDefinition;
if (SUCCEEDED(hr))
{
hr = spClass->GetMethod(methodName, 0, &spInParamsDefinition, NULL);
if (FAILED(hr))
{
wcout << L"WMI query for method " << className << L"." << (PCWSTR)methodName << L" failed with error 0x" << hex << hr << endl;
}
// GetMethod returns NULL for method that takes no input parameters.
}
if (SUCCEEDED(hr) && (spInParamsDefinition != NULL))
{
hr = spInParamsDefinition->SpawnInstance(0, &spInParams);
if (FAILED(hr))
{
wcout << L"WMI input parameter creation for method " << className << L"." << (PCWSTR)methodName << L" failed with error 0x" << hex << hr << endl;
}
}
else if (spInParamsDefinition == NULL)
{
hr = S_FALSE;
}
return hr;
}
HRESULT WmiAddVolumeInputParameter(_Inout_ IWbemClassObject* pParams, _In_ PCWSTR className, _In_ PCWSTR methodName, _In_ const wstring& volume)
{
HRESULT hr = S_OK;
SAFEARRAY* psa = NULL;
SAFEARRAYBOUND saBound = {};
saBound.lLbound = 0;
saBound.cElements = 1;
psa = SafeArrayCreate(VT_BSTR, 1, &saBound);
if (psa == NULL)
{
wcout << L"Out of memory creating safe-array" << endl;
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
long index = 0;
hr = SafeArrayPutElement(psa, &index, (BSTR)bstr_t(volume.c_str()));
if (FAILED(hr))
{
wcout << L"SafeArrayPutElement(0, " << volume << L") failed with error 0x" << hex << hr << endl;
}
}
VARIANT volumeArray;
VariantInit(&volumeArray);
volumeArray.vt = VT_ARRAY | VT_BSTR;
volumeArray.parray = psa;
if (SUCCEEDED(hr))
{
hr = pParams->Put(bstr_t(CIM_DEDUP_PROP_VOLUME), 0, &volumeArray, 0);
if (FAILED(hr))
{
wcout << L"Setting property " << className << L"." << methodName << L"." << CIM_DEDUP_PROP_VOLUME << L" failed with error 0x" << hex << hr << endl;
}
}
if (psa != NULL)
{
SafeArrayDestroy(psa);
}
return hr;
}
HRESULT WmiAddInputParameter(_Inout_ IWbemClassObject* pParams, _In_ PCWSTR className, _In_ PCWSTR methodName, _In_ PCWSTR propertyName, _In_ variant_t& var)
{
HRESULT hr = S_OK;
if (SUCCEEDED(hr))
{
hr = pParams->Put(bstr_t(propertyName), 0, &var, 0);
if (FAILED(hr))
{
wcout << L"Setting property " << className << L"." << methodName << L"." << propertyName << L" failed with error 0x" << hex << hr << endl;
}
}
return hr;
}
HRESULT WmiGetErrorInfo(_Out_ HRESULT& hrOperation, _Out_ wstring& errorMessageOperation)
{
CComPtr<IErrorInfo> spErrorInfo;
CComPtr<IWbemClassObject> spWmiError;
HRESULT hr = GetErrorInfo(0, &spErrorInfo);
if (FAILED(hr))
{
wcout << L"GetErrorInfo failed with error 0x" << hex << hr << endl;
}
if (SUCCEEDED(hr))
{
hr = spErrorInfo->QueryInterface(&spWmiError);
if (FAILED(hr))
{
wcout << L"IErrorInfo::QueryInterface failed with error 0x" << hex << hr << endl;
}
}
if (SUCCEEDED(hr))
{
variant_t var;
// Get the value of the ErrorCode property
hr = spWmiError->Get(CIM_DEDUP_PROP_ERRORCODE, 0, &var, 0, 0);
if (FAILED(hr))
{
wcout << L"IWbemClassObject::Get for property " << CIM_DEDUP_PROP_ERRORCODE << L" failed with error 0x" << hex << hr << endl;
}
else
{
// This is the root cause error
hrOperation = var.ulVal;
// Get the error message
hr = spWmiError->Get(CIM_DEDUP_PROP_ERRORMESSAGE, 0, &var, 0, 0);
if (FAILED(hr))
{
wcout << L"IWbemClassObject::Get for property " << CIM_DEDUP_PROP_ERRORMESSAGE << L" failed with error 0x" << hex << hr << endl;
}
else
{
errorMessageOperation = var.bstrVal;
}
}
}
return hr;
}
HRESULT WmiExecuteMethod(_In_ IWbemServices* pWmi, _In_ PCWSTR className, _In_ PCWSTR methodName, _In_opt_ IWbemClassObject* pInParams,
_Out_ CComPtr<IWbemClassObject>& spOutParams, _In_opt_ PCWSTR context)
{
// Make the method call
HRESULT hr = pWmi->ExecMethod(bstr_t(className), bstr_t(methodName), 0, NULL, pInParams, &spOutParams, NULL);
// Evaluate the output parameter object
if (SUCCEEDED(hr))
{
variant_t var;
hr = spOutParams->Get(bstr_t(CIM_DEDUP_PROP_RETURNVALUE), 0, &var, NULL, 0);
if (FAILED(hr))
{
wcout << L"Get method return value for " << className << L"." << methodName << L"(" << context << L")" << L" failed with error 0x" << hex << hr << endl;
}
if (SUCCEEDED(hr) && FAILED(var.ulVal))
{
hr = var.ulVal;
wcout << L"WMI method " << className << L"." << methodName << L"(" << context << L")" << L" failed with error 0x" << hex << hr << endl;
}
}
else
{
// Get the root cause error
HRESULT hrLocal = S_OK;
HRESULT hrOperation = S_OK;
wstring errorMessage;
hrLocal = WmiGetErrorInfo(hrOperation, errorMessage);
if (SUCCEEDED(hrLocal) && FAILED(hrOperation))
{
hr = hrOperation;
wcout << L"WMI method execution " << className << L"." << methodName << L"(" << context << L")" << L" failed with error 0x" << hex << hr << endl;
wcout << L" Error message: " << errorMessage << endl;
}
}
return hr;
}
HRESULT WmiGetDedupInstanceByVolumeId(PCWSTR className, _In_ const wstring& volumeGuidName, _Out_ CComPtr<IWbemClassObject>& instance)
{
CComPtr<IWbemServices> spWmi;
CComPtr<IWbemClassObject> spInstance;
HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi);
if (SUCCEEDED(hr))
{
wstring objectPath = className;
objectPath.append(L".VolumeId='");
objectPath.append(volumeGuidName);
objectPath.append(L"'");
// Get the specified object
hr = spWmi->GetObject(
bstr_t(objectPath.c_str()),
WBEM_FLAG_RETURN_WBEM_COMPLETE,
NULL, // context
&spInstance,
NULL // synchronous call; not needed
);
if (FAILED(hr))
{
// Get the root cause error
HRESULT hrLocal = S_OK;
HRESULT hrOperation = S_OK;
wstring errorMessage;
hrLocal = WmiGetErrorInfo(hrOperation, errorMessage);
if (SUCCEEDED(hrLocal) && FAILED(hrOperation))
{
if (hrOperation == DDP_E_NOT_FOUND || hrOperation == DDP_E_PATH_NOT_FOUND || hrOperation == DDP_E_VOLUME_DEDUP_DISABLED)
{
// The object is not found
hr = S_FALSE;
}
else
{
hr = hrOperation;
wcout << L"WMI query for " << objectPath << L" failed with error 0x" << hex << hr << endl;
wcout << L" Error message: " << errorMessage << endl;
}
}
}
}
if (SUCCEEDED(hr))
{
instance = spInstance;
}
return hr;
}
HRESULT WmiQueryDedupInstancesByVolumeId(_In_ IWbemServices* pWmi, _In_ const wstring& className, _In_ const wstring& volumeGuidName, _Out_ CComPtr<IEnumWbemClassObject>& instances)
{
CComPtr<IWbemServices> spWmi;
HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi);
// Backslash chars need to be escaped in WMI queries
wstring quotedVolumeGuidName = volumeGuidName;
StringReplace(quotedVolumeGuidName, L"\\", L"\\\\");
wstring query = L"select * from ";
query.append(className);
query.append(L" where VolumeId='");
query.append(quotedVolumeGuidName);
query.append(L"'");
hr = pWmi->ExecQuery(
bstr_t("WQL"),
bstr_t(query.c_str()),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&instances
);
if (FAILED(hr))
{
wcout << L"WMI query failed with error 0x" << hex << hr << L", query: " << query << endl;
}
return hr;
}
/////////////////////////////////////////////////////////////////////
//
// Common utilities
//
/////////////////////////////////////////////////////////////////////
void StringReplace(_Inout_ wstring& stringValue, _In_ const wstring& matchValue, _In_ const wstring& replaceValue)
{
if (!stringValue.empty())
{
if (matchValue.compare(replaceValue) != 0)
{
wstring::size_type pos = stringValue.find(matchValue, 0);
while (pos != wstring::npos)
{
stringValue.replace(
pos,
matchValue.length(),
replaceValue);
pos = stringValue.find(
matchValue,
pos + replaceValue.length()
);
}
}
else
{
// If the two values are the same, we don't need to replace anything
}
}
}
wstring TrimTrailingSeparator(_In_ const wstring& str, _In_ WCHAR separator)
{
wstring returnString = str;
std::wstring::size_type pos = returnString.find_last_not_of(separator);
if (pos != std::wstring::npos)
{
++pos;
}
else
{
pos = 0;
}
if (pos < returnString.length())
{
returnString.erase(pos);
}
return returnString;
}
bool IsRootPath(_In_ const wstring& path)
{
WCHAR volumePath[MAX_PATH];
bool isRootPath = false;
if (GetVolumePathName(path.c_str(), volumePath, MAX_PATH))
{
// For volume root, input length will equal output length (ignoring
// trailing backslash if any)
size_t cchPath = wcslen(path.c_str());
size_t cchRoot = wcslen(volumePath);
if (volumePath[cchRoot - 1] == L'\\')
cchRoot--;
if (path.c_str()[cchPath - 1] == L'\\')
cchPath--;
if (cchPath == cchRoot)
{
isRootPath = true;
}
}
return isRootPath;
}
void PrintUsage(_In_ LPCWSTR programName)
{
wcout << L"BACKUP" << endl;
wcout << L"Backup a directory to a destination directory:" << endl <<
L"\t" << programName << L" -backup <directory-or-volume-path> -destination <directory-path>" << endl << endl;
wcout << L"EXAMPLE: " << programName << L" -backup d:\\mydirectory -destination f:\\mydirectorybackup" << endl;
wcout << L"EXAMPLE: " << programName << L" -backup d:\\ -destination f:\\mydirectorybackup" << endl;
wcout << endl << L"RESTORE" << endl;
wcout << L"Restore a backed up directory to a destination directory:" << endl <<
L"\t" << programName << L" -restore <backup-directory-path> -destination <directory-path>" << endl << endl;
wcout << L"EXAMPLE: " << programName << L" -restore f:\\mydirectorybackup -destination d:\\mydirectory" << endl;
wcout << endl << L"SINGLE FILE RESTORE" << endl;
wcout << L"Restore the reparse point to the destination:" << endl <<
L"\t" << programName << L" -restorestub <backup-file-path> -destination <file-path>" << endl << endl;
wcout << L"Restore the data for the reparse point restored with the -restorestub option above:" << endl <<
L"\t" << programName << L" -restoredata <backup-file-path> -destination <stub-path>" << endl << endl;
wcout << L"Restore the reparse point and data in one operation (-restorestub + -restoredata):" << endl <<
L"\t" << programName << L" -restorefile <backup-file-path> -destination <file-path>" << endl << endl;
wcout << L"EXAMPLE: " << programName << L" -restorefile f:\\mydirectorybackup\\myfile -destination d:\\temp\\myfile" << endl;
wcout << endl << L"VOLUME RESTORE" << endl;
wcout << L"Restore the entire volume to the destination:" << endl <<
L"\t" << programName << L" -restorevolume <backup-directory-path> -destination <volume-path>" << endl << endl;
wcout << L"EXAMPLE: " << programName << L" -restorevolume f:\\mydirectorybackup -destination d:\\" << endl;
}
bool ParseCommandLine(_In_ int argc, _In_reads_(argc) _TCHAR* argv[], _Out_ Action *action, _Out_ wstring* source, _Out_ wstring* destination)
{
if (action == NULL || source == NULL || destination == NULL)
{
return false;
}
*action = BackupAction;
*source = L"";
*destination = L"";
if (argc < 5) return false;
// argv[0] is the program name, skip it
// argv[1] is the command, must be one of the following
wstring actionString = argv[1];
if (actionString == L"-backup")
{
*action = BackupAction;
}
else if (actionString == L"-restorestub")
{
*action = RestoreStubAction;
}
else if (actionString == L"-restoredata")
{
*action = RestoreDataAction;
}
else if (actionString == L"-restorefile")
{
*action = RestoreFileAction;
}
else if (actionString == L"-restorevolume")
{
*action = RestoreVolumeAction;
}
else if (actionString == L"-restore")
{
*action = RestoreFilesAction;
}
else
{
return false;
}
// argv[2] is the source
*source = argv[2];
// argv[4] is the destination
*destination = argv[4];
// argv[3] is always "-destination", check it for completeness
wstring arg3 = argv[3];
if (arg3 != L"-destination") return false;
return true;
}
HRESULT ModifyPrivilege(_In_ LPCTSTR szPrivilege, _In_ BOOL fEnable)
{
HRESULT hr = S_OK;
TOKEN_PRIVILEGES NewState = {};
LUID luid = {};
HANDLE hToken = NULL;
// Open the process token for this process.
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken ))
{
wcout << L"Failed OpenProcessToken" << endl;
return ERROR_FUNCTION_FAILED;
}
// Get the local unique ID for the privilege.
if ( !LookupPrivilegeValue( NULL,
szPrivilege,
&luid ))
{
CloseHandle( hToken );
wcout << L"Failed LookupPrivilegeValue" << endl;
return ERROR_FUNCTION_FAILED;
}
// Assign values to the TOKEN_PRIVILEGE structure.
NewState.PrivilegeCount = 1;
NewState.Privileges[0].Luid = luid;
NewState.Privileges[0].Attributes =
(fEnable ? SE_PRIVILEGE_ENABLED : 0);
// Adjust the token privilege.
if (!AdjustTokenPrivileges(hToken,
FALSE,
&NewState,
0,
NULL,
NULL))
{
wcout << L"Failed AdjustTokenPrivileges" << endl;
hr = ERROR_FUNCTION_FAILED;
}
// Close the handle.
CloseHandle(hToken);
return hr;
}
HRESULT GetVolumeGuidNameForPath(_In_ const wstring& path, _Out_ wstring& volumeGuidName)
{
HRESULT hr = S_OK;
WCHAR volumePathName[MAX_PATH];
BOOL result = GetVolumePathName(path.c_str(), volumePathName, MAX_PATH);
if (!result)
{
wcout << L"GetVolumePathName(" << path << L") failed with error " << GetLastError() << endl;
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr))
{
WCHAR tempVolumeGuidName[MAX_PATH];
result = GetVolumeNameForVolumeMountPoint(volumePathName, tempVolumeGuidName, MAX_PATH);
if (!result)
{
wcout << L"GetVolumeNameForVolumePathName(" << volumePathName << L") failed with error " << GetLastError() << endl;
hr = HRESULT_FROM_WIN32(GetLastError());
}
volumeGuidName = tempVolumeGuidName;
}
return hr;
}
// Required by RPC
_Must_inspect_result_
_Ret_maybenull_ _Post_writable_byte_size_(size)
void * __RPC_USER MIDL_user_allocate(size_t size)
{
return LocalAlloc(0, size);
}
HRESULT
GetFileSize(
__in const std::wstring& FilePath,
__out LARGE_INTEGER& FileSize
)
{
HRESULT hr = S_OK;
HANDLE fileHandle =
::CreateFile(
FilePath.c_str(),
FILE_READ_ATTRIBUTES,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (fileHandle == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(::GetLastError());
wcout << L"CreateFile(%s" << FilePath << L") failed with error " << GetLastError() << endl;
}
else
{
if (!::GetFileSizeEx(fileHandle, &FileSize))
{
hr = HRESULT_FROM_WIN32(::GetLastError());
wcout << L"GetFileSizeEx(%s" << FilePath << L") failed with error " << GetLastError() << endl;
}
}
return hr;
}