// 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 #include #include #include "ddpbackup.h" #include #include #include #include #include #include #include #include #include #include #include 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& 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* pRestoredFiles); HRESULT RestoreDirectoryTree(_In_ const wstring& source, _In_ const wstring& destination, _In_ const vector& sourceExcludePaths, _Out_opt_ vector* 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& spWmi); HRESULT WmiGetMethodInputParams(_In_ IWbemServices* pWmi, _In_ PCWSTR className, _In_ PCWSTR methodName, _Out_ CComPtr& 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& spOutParams, _In_opt_ PCWSTR context = L""); HRESULT WmiGetDedupInstanceByVolumeId(PCWSTR className, _In_ const wstring& volumeGuidName, _Out_ CComPtr& instance); HRESULT WmiQueryDedupInstancesByVolumeId(_In_ IWbemServices* pWmi, _In_ const wstring& className, _In_ const wstring& volumeGuidName, _Out_ CComPtr& 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 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, ¤tPosition, 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 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& 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 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 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 spWmi; HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi); CComPtr 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 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 spWmi; HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi); CComPtr 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 spOutParams; hr = WmiExecuteMethod(spWmi, CIM_DEDUP_CLASS_VOLUME, methodName.c_str(), spInParams, spOutParams, L"DataAccess"); } return hr; } HRESULT CancelDedupJobs(_In_ const wstring& volumeGuidName) { CComPtr spWmi; HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi); // Get the job instance queued/running for the specified volume CComPtr spInstances; if (SUCCEEDED(hr)) { hr = WmiQueryDedupInstancesByVolumeId(spWmi, CIM_DEDUP_CLASS_JOB, volumeGuidName.c_str(), spInstances); } // Stop each job while (SUCCEEDED(hr)) { CComPtr 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 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 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* pRestoredFiles) { HRESULT hr = S_OK; // Restore files (exclude deduplication store) vector 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 spJob; CComPtr 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" " L" "; 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 spWmi; HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi); CComPtr 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 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 spWmi; HRESULT hr = WmiGetWbemServices(CIM_DEDUP_NAMESPACE, spWmi); CComPtr 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 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 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& sourceExcludePaths, _Out_opt_ vector* 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& spWmi) { CComPtr 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& spInParams) { CComPtr 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 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 spErrorInfo; CComPtr 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& 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& instance) { CComPtr spWmi; CComPtr 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& instances) { CComPtr 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 -destination " << 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 -destination " << 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 -destination " << endl << endl; wcout << L"Restore the data for the reparse point restored with the -restorestub option above:" << endl << L"\t" << programName << L" -restoredata -destination " << endl << endl; wcout << L"Restore the reparse point and data in one operation (-restorestub + -restoredata):" << endl << L"\t" << programName << L" -restorefile -destination " << 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 -destination " << 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; }