/*++ Copyright (c) 1999-2001 Microsoft Corporation Abstract: @doc @module process.cpp | The processing functions for the VSS admin CLI @end Author: Adi Oltean [aoltean] 04/04/2000 TBD: Add comments. Revision History: Name Date Comments aoltean 04/04/2000 Created ssteiner 10/20/2000 Changed List SnapshotSets to use more limited VSS queries. --*/ ///////////////////////////////////////////////////////////////////////////// // Includes // The rest of includes are specified here #include "vssadmin.h" #include "vswriter.h" #include "vsbackup.h" //////////////////////////////////////////////////////////////////////// // Standard foo for file name aliasing. This code block must be after // all includes of VSS header files. // #ifdef VSS_FILE_ALIAS #undef VSS_FILE_ALIAS #endif #define VSS_FILE_ALIAS "ADMPROCC" // //////////////////////////////////////////////////////////////////////// #define VSSADM_ONE_MB ( 1024 * 1024 ) #define VSSADM_INFINITE_DIFFAREA 0xFFFFFFFFFFFFFFFF #define VSS_CTX_ATTRIB_MASK 0x01F ///////////////////////////////////////////////////////////////////////////// // Implementation class CVssAdmSnapshotSetEntry { public: // Constructor - Throws NOTHING CVssAdmSnapshotSetEntry( IN VSS_ID SnapshotSetId, IN INT nOriginalSnapshotsCount ) : m_SnapshotSetId( SnapshotSetId ), m_nOriginalSnapshotCount(nOriginalSnapshotsCount) { } ~CVssAdmSnapshotSetEntry() { // Have to delete all snapshots entries int iCount = GetSnapshotCount(); for ( int i = 0; i < iCount; ++i ) { VSS_SNAPSHOT_PROP *pSSProp; pSSProp = GetSnapshotAt( i ); ::VssFreeSnapshotProperties(pSSProp); delete pSSProp; } } // Add new snapshot to the snapshot set HRESULT AddSnapshot( IN CVssFunctionTracer &ft, IN VSS_SNAPSHOT_PROP *pVssSnapshotProp ) { HRESULT hr = S_OK; try { VSS_SNAPSHOT_PROP *pNewVssSnapshotProp = new VSS_SNAPSHOT_PROP; if ( pNewVssSnapshotProp == NULL ) ft.Throw( VSSDBG_VSSADMIN, E_OUTOFMEMORY, L"Out of memory" ); *pNewVssSnapshotProp = *pVssSnapshotProp; if ( !m_mapSnapshots.Add( pNewVssSnapshotProp->m_SnapshotId, pNewVssSnapshotProp ) ) { delete pNewVssSnapshotProp; ft.Throw( VSSDBG_VSSADMIN, E_OUTOFMEMORY, L"Out of memory" ); } } BS_STANDARD_CATCH(); return hr; } INT GetSnapshotCount() { return m_mapSnapshots.GetSize(); } INT GetOriginalSnapshotCount() { return m_nOriginalSnapshotCount; } VSS_ID GetSnapshotSetId() { return m_SnapshotSetId; } VSS_SNAPSHOT_PROP *GetSnapshotAt( IN int nIndex ) { BS_ASSERT( !(nIndex < 0 || nIndex >= GetSnapshotCount()) ); return m_mapSnapshots.GetValueAt( nIndex ); } private: VSS_ID m_SnapshotSetId; INT m_nOriginalSnapshotCount; CVssSimpleMap m_mapSnapshots; }; // This class queries the list of all snapshots and assembles from the query // the list of snapshotsets and the volumes which are in the snapshotset. class CVssAdmSnapshotSets { public: // Constructor - Throws HRESULTS CVssAdmSnapshotSets( IN LONG lSnapshotContext, IN VSS_ID FilteredSnapshotSetId, IN VSS_ID FilteredSnapshotId, IN VSS_ID FilteredProviderId, IN LPCWSTR pwszFilteredForVolume ) { CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdmSnapshotSets::CVssAdmSnapshotSets" ); // Create the coordinator object CComPtr pICoord; ft.LogVssStartupAttempt(); ft.hr = pICoord.CoCreateInstance( CLSID_VSSCoordinator ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // Set the context ft.hr = pICoord->SetContext( lSnapshotContext ); // // If access denied, don't stop, it probably is a backup operator making this // call. Continue. The coordinator will use the backup context. // if ( ft.HrFailed() && ft.hr != E_ACCESSDENIED ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"SetContext failed with hr = 0x%08lx", ft.hr); // Get list all snapshots CComPtr pIEnumSnapshots; ft.hr = pICoord->Query( GUID_NULL, VSS_OBJECT_NONE, VSS_OBJECT_SNAPSHOT, &pIEnumSnapshots ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Query failed with hr = 0x%08lx", ft.hr); // For all snapshots do... VSS_OBJECT_PROP Prop; VSS_SNAPSHOT_PROP& Snap = Prop.Obj.Snap; for(;;) { // Get next element ULONG ulFetched; ft.hr = pIEnumSnapshots->Next( 1, &Prop, &ulFetched ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Next failed with hr = 0x%08lx", ft.hr); // Test if the cycle is finished if (ft.hr == S_FALSE) { BS_ASSERT( ulFetched == 0); break; } // If filtering, skip entry if snapshot set id is not in the specified snapshot set if ( ( FilteredSnapshotSetId != GUID_NULL ) && !( Snap.m_SnapshotSetId == FilteredSnapshotSetId ) ) continue; // If filtering, skip entry if snapshot id is not in the specified snapshot set if ( ( FilteredSnapshotId != GUID_NULL ) && !( Snap.m_SnapshotId == FilteredSnapshotId ) ) continue; // If filtering, skip entry if provider ID is not in the specified snapshot if ( ( FilteredProviderId != GUID_NULL ) && !( Snap.m_ProviderId == FilteredProviderId ) ) continue; // If filtering, skip entry if FOR volume is not in the specified snapshot if ( ( pwszFilteredForVolume != NULL ) && ( pwszFilteredForVolume[0] != '\0' ) && ( ::_wcsicmp( pwszFilteredForVolume, Snap.m_pwszOriginalVolumeName ) != 0 ) ) continue; ft.Trace( VSSDBG_VSSADMIN, L"Snapshot: %s", Snap.m_pwszOriginalVolumeName ); // Look up the snapshot set id in the list of snapshot sets CVssAdmSnapshotSetEntry *pcSSE; pcSSE = m_mapSnapshotSets.Lookup( Snap.m_SnapshotSetId ); if ( pcSSE == NULL ) { // Haven't seen this snapshot set before, add it to list pcSSE = new CVssAdmSnapshotSetEntry( Snap.m_SnapshotSetId, Snap.m_lSnapshotsCount ); if ( pcSSE == NULL ) ft.Throw( VSSDBG_VSSADMIN, E_OUTOFMEMORY, L"Out of memory" ); if ( !m_mapSnapshotSets.Add( Snap.m_SnapshotSetId, pcSSE ) ) { delete pcSSE; ft.Throw( VSSDBG_VSSADMIN, E_OUTOFMEMORY, L"Out of memory" ); } } // Now add the snapshot to the snapshot set ft.hr = pcSSE->AddSnapshot( ft, &Snap ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"AddSnapshot failed" ); } } ~CVssAdmSnapshotSets() { CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdmSnapshotSets::~CVssAdmSnapshotSets" ); // Have to delete all int iCount; iCount = m_mapSnapshotSets.GetSize(); for ( int i = 0; i < iCount; ++i ) { delete m_mapSnapshotSets.GetValueAt( i ); } } INT GetSnapshotSetCount() { return m_mapSnapshotSets.GetSize(); } CVssAdmSnapshotSetEntry *GetSnapshotSetAt( IN int nIndex ) { BS_ASSERT( !(nIndex < 0 || nIndex >= GetSnapshotSetCount()) ); return m_mapSnapshotSets.GetValueAt( nIndex ); } private: CVssSimpleMap m_mapSnapshotSets; }; ///////////////////////////////////////////////////////////////////////////// // Implementation void CVssAdminCLI::PrintUsage( IN CVssFunctionTracer& ft ) throw(HRESULT) { // // Based on parsed command line type, print detailed command usage if // eAdmCmd is valid, else print general vssadmin usage // if ( m_sParsedCommand.eAdmCmd != VSSADM_C_INVALID ) { OutputMsg( ft, g_asAdmCommands[m_sParsedCommand.eAdmCmd].lMsgDetail, g_asAdmCommands[m_sParsedCommand.eAdmCmd].pwszMajorOption, g_asAdmCommands[m_sParsedCommand.eAdmCmd].pwszMinorOption); if ( g_asAdmCommands[m_sParsedCommand.eAdmCmd].bShowSSTypes ) DumpSnapshotTypes( ft ); return; } // // Print out header // OutputMsg( ft, MSG_USAGE ); // // Figure out the maximum command length to help with formatting // INT idx; INT iMaxLen = 0; for ( idx = VSSADM_C_FIRST; idx < VSSADM_C_NUM_COMMANDS; ++idx ) { if ( CVssSKU::GetSKU() & g_asAdmCommands[idx].dwSKUs ) { size_t cCmd; cCmd = ::wcslen( g_asAdmCommands[idx].pwszMajorOption ) + ::wcslen( g_asAdmCommands[idx].pwszMinorOption ) + 1; if ( iMaxLen < (INT)cCmd ) iMaxLen = (INT)cCmd; } } // // Get a string to hold the string // LPWSTR pwszCommand; pwszCommand = new WCHAR[iMaxLen + 1]; if (pwszCommand == NULL ) ft.Throw( VSSDBG_VSSADMIN, E_OUTOFMEMORY, L"CVssAdminCLI::PrintUsage: Can't get memory: %d", ::GetLastError() ); // // Go through the list of commands and print the general information // about each. // for ( idx = VSSADM_C_FIRST; idx < VSSADM_C_NUM_COMMANDS; ++idx ) { if ( CVssSKU::GetSKU() & g_asAdmCommands[idx].dwSKUs ) { // stick both parts of the command together ::wcscpy( pwszCommand, g_asAdmCommands[idx].pwszMajorOption ); ::wcscat( pwszCommand, L" " ); ::wcscat( pwszCommand, g_asAdmCommands[idx].pwszMinorOption ); // pad with spaces at the end for ( INT i = (INT) ::wcslen( pwszCommand); i < iMaxLen; ++i ) pwszCommand[i] = L' '; pwszCommand[iMaxLen] = L'\0'; OutputMsg( ft, g_asAdmCommands[idx].lMsgGen, pwszCommand ); } } delete[] pwszCommand; m_nReturnValue = VSS_CMDRET_SUCCESS; } void CVssAdminCLI::AddDiffArea( IN CVssFunctionTracer& ft ) throw(HRESULT) { VSS_ID ProviderId = GUID_NULL; // // Convert provider option to a VSS_ID // LPCWSTR pwszProvider = GetOptionValueStr( VSSADM_O_PROVIDER ); // // Determine if this is an ID or a name // if ( !ScanGuid( ft, pwszProvider, ProviderId ) ) { // Have a provider name, look it up if ( !GetProviderId( ft, pwszProvider, &ProviderId ) ) { // Provider name not found, print error OutputErrorMsg( ft, MSG_ERROR_PROVIDER_NAME_NOT_FOUND, pwszProvider ); return; } } // Create a Coordinator interface CComPtr pIMgmt; ft.LogVssStartupAttempt(); ft.hr = pIMgmt.CoCreateInstance( CLSID_VssSnapshotMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // Get the management object CComPtr pIDiffSnapMgmt; ft.hr = pIMgmt->GetProviderMgmtInterface( ProviderId, IID_IVssDifferentialSoftwareSnapshotMgmt, (IUnknown**)&pIDiffSnapMgmt ); if ( ft.HrFailed() ) { if ( ft.hr == E_NOINTERFACE ) { // The provider doesn't support this interface OutputErrorMsg( ft, MSG_ERROR_PROVIDER_DOESNT_SUPPORT_DIFFAREAS, pwszProvider ); return; } ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"GetProviderMgmtInterface failed with hr = 0x%08lx", ft.hr); } LONGLONG llMaxSize; if ( GetOptionValueNum( ft, VSSADM_O_MAXSIZE, &llMaxSize ) ) { if ( llMaxSize < VSSADM_ONE_MB ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_NUMBER, L"CVssAdminCLI::AddDiffarea: A maxsize of less than 1 MB specified, invalid"); } else { llMaxSize = VSSADM_INFINITE_DIFFAREA; } // Now add the assocation ft.hr = pIDiffSnapMgmt->AddDiffArea( GetOptionValueStr( VSSADM_O_FOR ), GetOptionValueStr( VSSADM_O_ON ), llMaxSize ); if ( ft.HrFailed() ) { switch( ft.hr ) { case VSS_E_OBJECT_ALREADY_EXISTS: OutputErrorMsg( ft, MSG_ERROR_ASSOCIATION_ALREADY_EXISTS ); break; default: ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"AddDiffArea failed with hr = 0x%08lx", ft.hr); break; } return; } // // Print results, if needed // if ( !IsQuiet() ) { OutputMsg( ft, MSG_INFO_ADDED_DIFFAREA ); } m_nReturnValue = VSS_CMDRET_SUCCESS; } void CVssAdminCLI::CreateSnapshot( IN CVssFunctionTracer& ft ) throw(HRESULT) { LONG lContext; // // First determine the snapshot type as specified by the user // lContext = DetermineSnapshotType( ft, GetOptionValueStr( VSSADM_O_SNAPTYPE ) ); // // There are two different ways snapshots are created. One is where // the backup components has to be used when writers have to be invoked, // and the other is to directly call the coordinator. // if ( lContext | VSS_VOLSNAP_ATTR_NO_WRITERS ) { CreateNoWritersSnapshot( lContext ); } } void CVssAdminCLI::DisplayDiffAreasPrivate( IN CVssFunctionTracer& ft, IVssEnumMgmtObject *pIEnumMgmt ) throw(HRESULT) { // For all diffareas do... VSS_MGMT_OBJECT_PROP Prop; VSS_DIFF_AREA_PROP& DiffArea = Prop.Obj.DiffArea; for(;;) { // Get next element ULONG ulFetched; ft.hr = pIEnumMgmt->Next( 1, &Prop, &ulFetched ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Next failed with hr = 0x%08lx", ft.hr); // Test if the cycle is finished if (ft.hr == S_FALSE) { BS_ASSERT( ulFetched == 0); break; } LPWSTR pwszUsedSpace, pwszAllocatedSpace, pwszMaxSpace; pwszUsedSpace = FormatNumber( ft, DiffArea.m_llUsedDiffSpace ); pwszAllocatedSpace = FormatNumber( ft, DiffArea.m_llAllocatedDiffSpace ); pwszMaxSpace = FormatNumber( ft, DiffArea.m_llMaximumDiffSpace ); OutputMsg( ft, MSG_INFO_SNAPSHOT_STORAGE_CONTENTS, DiffArea.m_pwszVolumeName, DiffArea.m_pwszDiffAreaVolumeName, pwszUsedSpace, pwszAllocatedSpace, pwszMaxSpace ); ::VssFreeString(pwszUsedSpace); ::VssFreeString(pwszAllocatedSpace); ::VssFreeString(pwszMaxSpace); ::CoTaskMemFree(DiffArea.m_pwszVolumeName); ::CoTaskMemFree(DiffArea.m_pwszDiffAreaVolumeName); } } void CVssAdminCLI::ListDiffAreas( IN CVssFunctionTracer& ft ) throw(HRESULT) { // Make sure user didn't specify both /On and /For if ( GetOptionValueStr( VSSADM_O_FOR ) != NULL && GetOptionValueStr( VSSADM_O_ON ) != NULL ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_SET_OF_OPTIONS, L"CVssAdminCLI::ListDiffAreas: Can't specify both ON and FOR volume options" ); VSS_ID ProviderId = GUID_NULL; // // Convert provider option to a VSS_ID // LPCWSTR pwszProvider = GetOptionValueStr( VSSADM_O_PROVIDER ); // // Determine if this is an ID or a name // if ( !ScanGuid( ft, pwszProvider, ProviderId ) ) { // Have a provider name, look it up if ( !GetProviderId( ft, pwszProvider, &ProviderId ) ) { // Provider name not found, print error OutputErrorMsg( ft, MSG_ERROR_PROVIDER_NAME_NOT_FOUND, pwszProvider ); return; } } // Create a Coordinator interface CComPtr pIMgmt; ft.LogVssStartupAttempt(); ft.hr = pIMgmt.CoCreateInstance( CLSID_VssSnapshotMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // Get the management object CComPtr pIDiffSnapMgmt; ft.hr = pIMgmt->GetProviderMgmtInterface( ProviderId, IID_IVssDifferentialSoftwareSnapshotMgmt, (IUnknown**)&pIDiffSnapMgmt ); if ( ft.HrFailed() ) { if ( ft.hr == E_NOINTERFACE ) { // The provider doesn't support this interface OutputErrorMsg( ft, MSG_ERROR_PROVIDER_DOESNT_SUPPORT_DIFFAREAS, pwszProvider ); return; } ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"GetProviderMgmtInterface failed with hr = 0x%08lx", ft.hr); } // See if query by for volume if ( GetOptionValueStr( VSSADM_O_FOR ) != NULL ) { // Query by For volume CComPtr pIEnumMgmt; ft.hr = pIDiffSnapMgmt->QueryDiffAreasForVolume( GetOptionValueStr( VSSADM_O_FOR ), &pIEnumMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"QueryDiffAreasForVolume failed, hr = 0x%08lx", ft.hr); if ( ft.hr == S_FALSE ) // empty query ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_NO_ITEMS_IN_QUERY, L"CVssAdminCLI::ListDiffareas: No diffareas found that satisfy the query" ); DisplayDiffAreasPrivate( ft, pIEnumMgmt ); } else if ( GetOptionValueStr( VSSADM_O_ON ) != NULL ) { // Query by On volume CComPtr pIEnumMgmt; ft.hr = pIDiffSnapMgmt->QueryDiffAreasOnVolume( GetOptionValueStr( VSSADM_O_ON ), &pIEnumMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"QueryDiffAreasOnVolume failed, hr = 0x%08lx", ft.hr); if ( ft.hr == S_FALSE ) // empty query ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_NO_ITEMS_IN_QUERY, L"CVssAdminCLI::ListDiffareas: No diffareas found that satisfy the query" ); DisplayDiffAreasPrivate( ft, pIEnumMgmt ); } else { // Query all diff areas // // Get the list of all volumes // CComPtr pIEnumMgmt; ft.hr = pIMgmt->QueryVolumesSupportedForSnapshots( ProviderId, VSS_CTX_ALL, &pIEnumMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"QueryVolumesSupportedForSnapshots failed, hr = 0x%08lx", ft.hr); if ( ft.hr == S_FALSE ) // empty query ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_NO_ITEMS_IN_QUERY, L"CVssAdminCLI::ListDiffareas: No diffareas found that satisfy the query" ); // // Query each volume to see if diff areas exist. // VSS_MGMT_OBJECT_PROP Prop; VSS_VOLUME_PROP& VolProp = Prop.Obj.Vol; for(;;) { // Get next element ULONG ulFetched; ft.hr = pIEnumMgmt->Next( 1, &Prop, &ulFetched ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Next failed with hr = 0x%08lx", ft.hr); // Test if the cycle is finished if (ft.hr == S_FALSE) { BS_ASSERT( ulFetched == 0); break; } // For all volumes do... CComPtr pIEnumMgmtDiffArea; ft.hr = pIDiffSnapMgmt->QueryDiffAreasForVolume( VolProp.m_pwszVolumeName, &pIEnumMgmtDiffArea ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"QueryDiffAreasForVolume failed, hr = 0x%08lx", ft.hr); if ( ft.hr == S_FALSE ) // empty query continue; DisplayDiffAreasPrivate( ft, pIEnumMgmtDiffArea ); ::CoTaskMemFree(VolProp.m_pwszVolumeName); ::CoTaskMemFree(VolProp.m_pwszVolumeDisplayName); } } m_nReturnValue = VSS_CMDRET_SUCCESS; } void CVssAdminCLI::ListSnapshots( IN CVssFunctionTracer& ft ) throw(HRESULT) { bool bNonEmptyResult = false; // See if we have to filter by snapshot set id VSS_ID guidSSID = GUID_NULL; if ( GetOptionValueStr( VSSADM_O_SET ) != NULL ) { // Get GUID if ( !ScanGuid( ft, GetOptionValueStr( VSSADM_O_SET ), guidSSID ) ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_OPTION_VALUE, L"CVssAdminCLI::ListSnapshots: invalid snapshot set ID: %s", GetOptionValueStr( VSSADM_O_SET ) ); } // Set if we have to filter by snapshot id VSS_ID guidSnapID = GUID_NULL; if ( GetOptionValueStr( VSSADM_O_SNAPSHOT ) != NULL ) { // Can't specify both snapshot set id and snapshot id if ( guidSSID != GUID_NULL ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_SET_OF_OPTIONS, L"CVssAdminCLI::ListSnapshots: Cannot specify both /SET and /SNAPSHOT options at the same time" ); // Get GUID if ( !ScanGuid( ft, GetOptionValueStr( VSSADM_O_SNAPSHOT ), guidSnapID ) ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_OPTION_VALUE, L"CVssAdminCLI::ListSnapshots: invalid snapshot ID: %s", GetOptionValueStr( VSSADM_O_SNAPSHOT ) ); } // Figure out snapshot context LONG lSnapshotContext; lSnapshotContext = DetermineSnapshotType( ft, GetOptionValueStr( VSSADM_O_SNAPTYPE ) ); // See if we have to filter by volume name WCHAR wszVolumeNameInternal[nLengthOfVolMgmtVolumeName + 1] = L""; if ( GetOptionValueStr( VSSADM_O_FOR ) != NULL ) { // Calculate the unique volume name, just to make sure that we have the right path // If FOR volume name starts with the '\', assume it is already in the correct volume name format. // This is important for transported volumes since GetVolumeNameForVolumeMountPointW() won't work. if ( GetOptionValueStr( VSSADM_O_FOR )[0] != L'\\' ) { if (!::GetVolumeNameForVolumeMountPointW( GetOptionValueStr( VSSADM_O_FOR ), wszVolumeNameInternal, ARRAY_LEN(wszVolumeNameInternal))) ft.Throw( VSSDBG_COORD, VSS_E_OBJECT_NOT_FOUND, L"GetVolumeNameForVolumeMountPoint(%s,...) " L"failed with error code 0x%08lx", GetOptionValueStr( VSSADM_O_FOR ), ::GetLastError()); } else ::wcsncpy( wszVolumeNameInternal, GetOptionValueStr( VSSADM_O_FOR ), STRING_LEN(wszVolumeNameInternal) ); } // // See if we have to filter by provider // VSS_ID ProviderId = GUID_NULL; LPCWSTR pwszProvider = GetOptionValueStr( VSSADM_O_PROVIDER ); if ( pwszProvider != NULL ) { // // Determine if this is an ID or a name // if ( !ScanGuid( ft, pwszProvider, ProviderId ) ) { // Have a provider name, look it up if ( !GetProviderId( ft, pwszProvider, &ProviderId ) ) { // Provider name not found, print error OutputErrorMsg( ft, MSG_ERROR_PROVIDER_NAME_NOT_FOUND, pwszProvider ); return; } } } // Query the snapshots CVssAdmSnapshotSets cVssAdmSS( lSnapshotContext, guidSSID, guidSnapID, ProviderId, wszVolumeNameInternal ); INT iSnapshotSetCount = cVssAdmSS.GetSnapshotSetCount(); // If there are no present snapshots then display a message. if (iSnapshotSetCount == 0) { ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_NO_ITEMS_IN_QUERY, L"CVssAdminCLI::ListSnapshots: No snapshots found that satisfy the query"); } // For all snapshot sets do... for ( INT iSSS = 0; iSSS < iSnapshotSetCount; ++iSSS ) { CVssAdmSnapshotSetEntry *pcSSE; pcSSE = cVssAdmSS.GetSnapshotSetAt( iSSS ); BS_ASSERT( pcSSE != NULL ); LPWSTR pwszGuid; LPWSTR pwszDateTime; pwszGuid = ::GuidToString( ft, pcSSE->GetSnapshotSetId() ); pwszDateTime = ::DateTimeToString( ft, &( pcSSE->GetSnapshotAt( 0 )->m_tsCreationTimestamp ) ); // Print each snapshot set OutputMsg( ft, MSG_INFO_SNAPSHOT_SET_HEADER, pwszGuid, pcSSE->GetOriginalSnapshotCount(), pwszDateTime ); ::VssFreeString( pwszGuid ); ::VssFreeString( pwszDateTime ); INT iSnapshotCount = pcSSE->GetSnapshotCount(); VSS_SNAPSHOT_PROP *pSnap; for( INT iSS = 0; iSS < iSnapshotCount; ++iSS ) { pSnap = pcSSE->GetSnapshotAt( iSS ); BS_ASSERT( pSnap != NULL ); // Get the provider name LPCWSTR pwszProviderName = GetProviderName(ft, pSnap->m_ProviderId); LPWSTR pwszAttributeStr = BuildSnapshotAttributeDisplayString( ft, pSnap->m_lSnapshotAttributes ); LPWSTR pwszSnapshotType = DetermineSnapshotType( ft, pSnap->m_lSnapshotAttributes ); // Print each snapshot pwszGuid = ::GuidToString( ft, pSnap->m_SnapshotId ); OutputMsg( ft, MSG_INFO_SNAPSHOT_CONTENTS, pwszGuid, pSnap->m_pwszOriginalVolumeName, pSnap->m_pwszSnapshotDeviceObject, pSnap->m_pwszOriginatingMachine ? pSnap->m_pwszOriginatingMachine : L"", pSnap->m_pwszServiceMachine ? pSnap->m_pwszServiceMachine : L"", // fix this when the idl file changes pwszProviderName ? pwszProviderName : L"?", pwszSnapshotType, pwszAttributeStr ); ::VssFreeString( pwszGuid ); ::VssFreeString( pwszAttributeStr ); ::VssFreeString( pwszSnapshotType ); bNonEmptyResult = true; } } m_nReturnValue = bNonEmptyResult? VSS_CMDRET_SUCCESS: VSS_CMDRET_EMPTY_RESULT; } void CVssAdminCLI::DumpSnapshotTypes( IN CVssFunctionTracer& ft ) throw(HRESULT) { // // Dump list of snapshot types based on SKU // INT idx; // Determine type of snapshot for ( idx = 0; g_asAdmTypeNames[idx].pwszName != NULL; ++idx ) { if ( CVssSKU::GetSKU() & g_asAdmTypeNames[idx].dwSKUs ) { OutputOnConsole( L" " ); OutputOnConsole( g_asAdmTypeNames[idx].pwszName ); OutputOnConsole( L"\r\n" ); } } UNREFERENCED_PARAMETER( ft ); } void CVssAdminCLI::ListWriters( IN CVssFunctionTracer& ft ) throw(HRESULT) { bool bNonEmptyResult = false; // Get the backup components object CComPtr pBackupComp; CComPtr pAsync; ft.hr = ::CreateVssBackupComponents(&pBackupComp); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"CreateVssBackupComponents failed with hr = 0x%08lx", ft.hr); // BUGBUG Initialize for backup ft.hr = pBackupComp->InitializeForBackup(); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"InitializeForBackup failed with hr = 0x%08lx", ft.hr); UINT unWritersCount; // get metadata for all writers ft.hr = pBackupComp->GatherWriterMetadata(&pAsync); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"GatherWriterMetadata failed with hr = 0x%08lx", ft.hr); // Using polling, try to obtain the list of writers as soon as possible HRESULT hrReturned = S_OK; for (int nRetries = 0; nRetries < MAX_RETRIES_COUNT; nRetries++ ) { // Wait a little ::Sleep(nPollingInterval); // Check if finished INT nReserved = 0; ft.hr = pAsync->QueryStatus( &hrReturned, &nReserved ); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"IVssAsync::QueryStatus failed with hr = 0x%08lx", ft.hr); if (hrReturned == VSS_S_ASYNC_FINISHED) break; if (hrReturned == VSS_S_ASYNC_PENDING) continue; ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"IVssAsync::QueryStatus returned hr = 0x%08lx", hrReturned); } // If still not ready, then print the "waiting for responses" message and wait. if (hrReturned == VSS_S_ASYNC_PENDING) { OutputMsg( ft, MSG_INFO_WAITING_RESPONSES ); ft.hr = pAsync->Wait(); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"IVssAsync::Wait failed with hr = 0x%08lx", ft.hr); } pAsync = NULL; // Gather the status of all writers ft.hr = pBackupComp->GatherWriterStatus(&pAsync); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"GatherWriterMetadata failed with hr = 0x%08lx", ft.hr); ft.hr = pAsync->Wait(); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"IVssAsync::Wait failed with hr = 0x%08lx", ft.hr); pAsync = NULL; ft.hr = pBackupComp->GetWriterStatusCount(&unWritersCount); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"GetWriterStatusCount failed with hr = 0x%08lx", ft.hr); // Print each writer status+supplementary info for(UINT unIndex = 0; unIndex < unWritersCount; unIndex++) { VSS_ID idInstance; VSS_ID idWriter; CComBSTR bstrWriter; VSS_WRITER_STATE eStatus; HRESULT hrWriterFailure; // Get the status for the (unIndex)-th writer ft.hr = pBackupComp->GetWriterStatus(unIndex, &idInstance, &idWriter, &bstrWriter, &eStatus, &hrWriterFailure); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"GetWriterStatus failed with hr = 0x%08lx", ft.hr); // Get the status description strings LPCWSTR pwszStatusDescription; switch (eStatus) { case VSS_WS_STABLE: pwszStatusDescription = LoadString( ft, IDS_WRITER_STATUS_STABLE); break; case VSS_WS_WAITING_FOR_FREEZE: pwszStatusDescription = LoadString( ft, IDS_WRITER_STATUS_WAITING_FOR_FREEZE); break; case VSS_WS_WAITING_FOR_THAW: pwszStatusDescription = LoadString( ft, IDS_WRITER_STATUS_FROZEN); break; case VSS_WS_WAITING_FOR_BACKUP_COMPLETE: pwszStatusDescription = LoadString( ft, IDS_WRITER_STATUS_WAITING_FOR_COMPLETION); break; case VSS_WS_FAILED_AT_IDENTIFY: case VSS_WS_FAILED_AT_PREPARE_BACKUP: case VSS_WS_FAILED_AT_PREPARE_SNAPSHOT: case VSS_WS_FAILED_AT_FREEZE: case VSS_WS_FAILED_AT_THAW: pwszStatusDescription = LoadString( ft, IDS_WRITER_STATUS_FAILED); break; default: pwszStatusDescription = LoadString( ft, IDS_WRITER_STATUS_UNKNOWN); break; } BS_ASSERT(pwszStatusDescription); LPCWSTR pwszWriterError; switch ( hrWriterFailure ) { case S_OK: pwszWriterError = LoadString ( ft, IDS_WRITER_ERROR_SUCCESS ); break; case VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT: pwszWriterError = LoadString( ft, IDS_WRITER_ERROR_INCONSISTENTSNAPSHOT); break; case VSS_E_WRITERERROR_OUTOFRESOURCES: pwszWriterError = LoadString( ft, IDS_WRITER_ERROR_OUTOFRESOURCES); break; case VSS_E_WRITERERROR_TIMEOUT: pwszWriterError = LoadString( ft, IDS_WRITER_ERROR_TIMEOUT); break; case VSS_E_WRITERERROR_RETRYABLE: pwszWriterError = LoadString( ft, IDS_WRITER_ERROR_RETRYABLE); break; case VSS_E_WRITERERROR_NONRETRYABLE: pwszWriterError = LoadString( ft, IDS_WRITER_ERROR_NONRETRYABLE); break; default: pwszWriterError = LoadString( ft, IDS_WRITER_ERROR_UNEXPECTED); ft.Trace( VSSDBG_VSSADMIN, L"Unexpected writer error failure: 0x%08x", hrWriterFailure ); break; } LPWSTR pwszWriterId = ::GuidToString( ft, idWriter ); LPWSTR pwszInstanceId = ::GuidToString( ft, idInstance ); OutputMsg( ft, MSG_INFO_WRITER_CONTENTS, (LPWSTR)bstrWriter ? (LPWSTR)bstrWriter : L"", pwszWriterId, pwszInstanceId, (INT)eStatus, pwszStatusDescription, pwszWriterError ); ::VssFreeString(pwszWriterId); ::VssFreeString(pwszInstanceId); bNonEmptyResult = true; } ft.hr = pBackupComp->FreeWriterStatus(); if (ft.HrFailed()) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"FreeWriterStatus failed with hr = 0x%08lx", ft.hr); m_nReturnValue = bNonEmptyResult? VSS_CMDRET_SUCCESS: VSS_CMDRET_EMPTY_RESULT; } void CVssAdminCLI::ListProviders( IN CVssFunctionTracer& ft ) throw(HRESULT) { bool bNonEmptyResult = false; // Create the coordinator object CComPtr pICoord; ft.LogVssStartupAttempt(); ft.hr = pICoord.CoCreateInstance( CLSID_VSSCoordinator ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // Query all (filtered) snapshot sets CComPtr pIEnumProv; ft.hr = pICoord->Query( GUID_NULL, VSS_OBJECT_NONE, VSS_OBJECT_PROVIDER, &pIEnumProv ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Query failed with hr = 0x%08lx", ft.hr); // For all snapshot sets do... VSS_OBJECT_PROP Prop; VSS_PROVIDER_PROP& Prov = Prop.Obj.Prov; for(;;) { // Get next element ULONG ulFetched; ft.hr = pIEnumProv->Next( 1, &Prop, &ulFetched ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Next failed with hr = 0x%08lx", ft.hr); // Test if the cycle is ended if (ft.hr == S_FALSE) { BS_ASSERT( ulFetched == 0); break; } // Get the provider type strings LPCWSTR pwszProviderType; switch (Prov.m_eProviderType) { case VSS_PROV_SYSTEM: pwszProviderType = LoadString( ft, IDS_PROV_TYPE_SYSTEM); break; case VSS_PROV_SOFTWARE: pwszProviderType = LoadString( ft, IDS_PROV_TYPE_SOFTWARE); break; case VSS_PROV_HARDWARE: pwszProviderType = LoadString( ft, IDS_PROV_TYPE_HARDWARE); break; default: pwszProviderType = LoadString( ft, IDS_PROV_TYPE_UNKNOWN); break; } BS_ASSERT(pwszProviderType); // Print each snapshot set LPWSTR pwszProviderId; pwszProviderId = ::GuidToString( ft, Prov.m_ProviderId ); OutputMsg( ft, MSG_INFO_PROVIDER_CONTENTS, Prov.m_pwszProviderName? Prov.m_pwszProviderName: L"", pwszProviderType, pwszProviderId, Prov.m_pwszProviderVersion ? Prov.m_pwszProviderVersion: L""); ::VssFreeString(pwszProviderId); ::CoTaskMemFree(Prov.m_pwszProviderName); ::CoTaskMemFree(Prov.m_pwszProviderVersion); bNonEmptyResult = true; } m_nReturnValue = bNonEmptyResult? VSS_CMDRET_SUCCESS: VSS_CMDRET_EMPTY_RESULT; } void CVssAdminCLI::ListVolumes( IN CVssFunctionTracer& ft ) throw(HRESULT) { LONG lContext; VSS_ID ProviderId = GUID_NULL; // // First determine the snapshot type as specified by the user // lContext = DetermineSnapshotType( ft, GetOptionValueStr( VSSADM_O_SNAPTYPE ) ); // // Convert provider option to a VSS_ID // LPCWSTR pwszProvider = GetOptionValueStr( VSSADM_O_PROVIDER ); // // Determine if this is an ID or a name // if ( !ScanGuid( ft, pwszProvider, ProviderId ) ) { // Have a provider name, look it up if ( !GetProviderId( ft, pwszProvider, &ProviderId ) ) { // Provider name not found, print error OutputErrorMsg( ft, MSG_ERROR_PROVIDER_NAME_NOT_FOUND, pwszProvider ); return; } } // Create a Coordinator interface CComPtr pIMgmt; ft.LogVssStartupAttempt(); ft.hr = pIMgmt.CoCreateInstance( CLSID_VssSnapshotMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // // Get the list of all volumes // CComPtr pIEnumMgmt; ft.hr = pIMgmt->QueryVolumesSupportedForSnapshots( ProviderId, lContext, &pIEnumMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"QueryVolumesSupportedForSnapshots failed, hr = 0x%08lx", ft.hr); if ( ft.hr == S_FALSE ) // empty query ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_NO_ITEMS_IN_QUERY, L"CVssAdminCLI::ListVolumes: No volumes found that satisfy the query" ); // // Query each volume to see if diff areas exist. // VSS_MGMT_OBJECT_PROP Prop; VSS_VOLUME_PROP& VolProp = Prop.Obj.Vol; for(;;) { // Get next element ULONG ulFetched; ft.hr = pIEnumMgmt->Next( 1, &Prop, &ulFetched ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Next failed with hr = 0x%08lx", ft.hr); // Test if the cycle is finished if (ft.hr == S_FALSE) { BS_ASSERT( ulFetched == 0); break; } OutputMsg( ft, MSG_INFO_VOLUME_CONTENTS, VolProp.m_pwszVolumeDisplayName, VolProp.m_pwszVolumeName ); ::CoTaskMemFree(VolProp.m_pwszVolumeName); ::CoTaskMemFree(VolProp.m_pwszVolumeDisplayName); } m_nReturnValue = VSS_CMDRET_SUCCESS; } void CVssAdminCLI::ResizeDiffArea( IN CVssFunctionTracer& ft ) throw(HRESULT) { VSS_ID ProviderId = GUID_NULL; // // Convert provider option to a VSS_ID // LPCWSTR pwszProvider = GetOptionValueStr( VSSADM_O_PROVIDER ); // // Determine if this is an ID or a name // if ( !ScanGuid( ft, pwszProvider, ProviderId ) ) { // Have a provider name, look it up if ( !GetProviderId( ft, pwszProvider, &ProviderId ) ) { // Provider name not found, print error OutputErrorMsg( ft, MSG_ERROR_PROVIDER_NAME_NOT_FOUND, pwszProvider ); return; } } // Create a Coordinator interface CComPtr pIMgmt; ft.LogVssStartupAttempt(); ft.hr = pIMgmt.CoCreateInstance( CLSID_VssSnapshotMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // Get the management object CComPtr pIDiffSnapMgmt; ft.hr = pIMgmt->GetProviderMgmtInterface( ProviderId, IID_IVssDifferentialSoftwareSnapshotMgmt, (IUnknown**)&pIDiffSnapMgmt ); if ( ft.HrFailed() ) { if ( ft.hr == E_NOINTERFACE ) { // The provider doesn't support this interface OutputErrorMsg( ft, MSG_ERROR_PROVIDER_DOESNT_SUPPORT_DIFFAREAS, pwszProvider ); return; } ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"GetProviderMgmtInterface failed with hr = 0x%08lx", ft.hr); } LONGLONG llMaxSize; if ( GetOptionValueNum( ft, VSSADM_O_MAXSIZE, &llMaxSize ) ) { if ( llMaxSize > -1 && llMaxSize < VSSADM_ONE_MB ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_NUMBER, L"CVssAdminCLI::ResizeDiffarea: maxsize of less than 1 MB specified, invalid"); } else { llMaxSize = VSSADM_INFINITE_DIFFAREA; } // Now add the assocation ft.hr = pIDiffSnapMgmt->ChangeDiffAreaMaximumSize( GetOptionValueStr( VSSADM_O_FOR ), GetOptionValueStr( VSSADM_O_ON ), llMaxSize ); if ( ft.HrFailed() ) { if ( ft.hr == VSS_E_OBJECT_NOT_FOUND ) // should be VSS_E_MAXIMUM_DIFFAREA_ASSOCIATIONS { // The associations was not found OutputErrorMsg( ft, MSG_ERROR_ASSOCIATION_NOT_FOUND ); return; } else ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"ResizeDiffArea failed with hr = 0x%08lx", ft.hr); } // // Print results, if needed // if ( !IsQuiet() ) { OutputMsg( ft, MSG_INFO_RESIZED_DIFFAREA ); } m_nReturnValue = VSS_CMDRET_SUCCESS; } void CVssAdminCLI::DeleteDiffAreas( IN CVssFunctionTracer& ft ) throw(HRESULT) { VSS_ID ProviderId = GUID_NULL; // // Convert provider option to a VSS_ID // LPCWSTR pwszProvider = GetOptionValueStr( VSSADM_O_PROVIDER ); // // Determine if this is an ID or a name // if ( !ScanGuid( ft, pwszProvider, ProviderId ) ) { // Have a provider name, look it up if ( !GetProviderId( ft, pwszProvider, &ProviderId ) ) { // Provider name not found, print error OutputErrorMsg( ft, MSG_ERROR_PROVIDER_NAME_NOT_FOUND, pwszProvider ); return; } } // Create a Coordinator interface CComPtr pIMgmt; ft.LogVssStartupAttempt(); ft.hr = pIMgmt.CoCreateInstance( CLSID_VssSnapshotMgmt ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // Get the management object CComPtr pIDiffSnapMgmt; ft.hr = pIMgmt->GetProviderMgmtInterface( ProviderId, IID_IVssDifferentialSoftwareSnapshotMgmt, (IUnknown**)&pIDiffSnapMgmt ); if ( ft.HrFailed() ) { if ( ft.hr == E_NOINTERFACE ) { // The provider doesn't support this interface OutputErrorMsg( ft, MSG_ERROR_PROVIDER_DOESNT_SUPPORT_DIFFAREAS, pwszProvider ); return; } ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"GetProviderMgmtInterface failed with hr = 0x%08lx", ft.hr); } // Now delete the assocation by changing the size to zero ft.hr = pIDiffSnapMgmt->ChangeDiffAreaMaximumSize( GetOptionValueStr( VSSADM_O_FOR ), GetOptionValueStr( VSSADM_O_ON ), 0 ); if ( ft.HrFailed() ) { if ( ft.hr == VSS_E_OBJECT_NOT_FOUND ) // should be VSS_E_MAXIMUM_DIFFAREA_ASSOCIATIONS { // The associations was not found OutputErrorMsg( ft, MSG_ERROR_ASSOCIATION_NOT_FOUND ); return; } else if ( ft.hr == VSS_E_VOLUME_IN_USE ) { // Can't delete associations that are in use OutputErrorMsg( ft, MSG_ERROR_ASSOCIATION_IS_IN_USE ); return; } else ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"ResizeDiffArea to 0 failed with hr = 0x%08lx", ft.hr); } // // Print results, if needed // if ( !IsQuiet() ) { OutputMsg( ft, MSG_INFO_DELETED_DIFFAREAS ); } m_nReturnValue = VSS_CMDRET_SUCCESS; } void CVssAdminCLI::DeleteSnapshots( IN CVssFunctionTracer& ft ) throw(HRESULT) { LONG lNumDeleted = 0; if ( GetOptionValueStr( VSSADM_O_SNAPSHOT ) ) { // // Delete by snapshot id // if ( GetOptionValueStr( VSSADM_O_SNAPTYPE ) != NULL || GetOptionValueStr( VSSADM_O_FOR ) != NULL ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_SET_OF_OPTIONS, L"CVssAdminCLI::DeleteSnapshots: Can't specify both ON and/or FOR volume options with SNAPSHOT option" ); // Make sure the user didn't specify any of the other optional options if ( GetOptionValueStr( VSSADM_O_FOR ) != NULL || GetOptionValueStr( VSSADM_O_SNAPTYPE ) != NULL || GetOptionValueBool( VSSADM_O_OLDEST ) ) { ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_SET_OF_OPTIONS, L"CVssAdminCLI::DeleteSnapshots: Invalid set of options" ); } VSS_ID SnapshotId = GUID_NULL; if ( !ScanGuid( ft, GetOptionValueStr( VSSADM_O_SNAPSHOT ), SnapshotId ) ) { ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_OPTION_VALUE, L"CVssAdminCLI::DeleteSnapshots: Invalid snapshot id" ); } // // Let's try to delete the snapshot // if ( PromptUserForConfirmation( ft, MSG_INFO_PROMPT_USER_FOR_DELETE_SNAPSHOTS, 1 ) ) { // Create the coordinator object CComPtr pICoord; ft.LogVssStartupAttempt(); ft.hr = pICoord.CoCreateInstance( CLSID_VSSCoordinator ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // Set all context ft.hr = pICoord->SetContext( VSS_CTX_ALL ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"SetContext failed with hr = 0x%08lx", ft.hr); VSS_ID NondeletedSnapshotId; ft.hr = pICoord->DeleteSnapshots( SnapshotId, VSS_OBJECT_SNAPSHOT, TRUE, &lNumDeleted, &NondeletedSnapshotId ); if ( ft.hr == VSS_E_OBJECT_NOT_FOUND ) { OutputErrorMsg( ft, MSG_ERROR_SNAPSHOT_NOT_FOUND, GetOptionValueStr( VSSADM_O_SNAPSHOT ) ); } else if ( ft.HrFailed() ) { ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"DeleteSnapshots failed with hr = 0x%08lx", ft.hr); } } } else { // // Delete by FOR option // if ( GetOptionValueStr( VSSADM_O_SNAPTYPE ) == NULL || GetOptionValueStr( VSSADM_O_FOR ) == NULL || GetOptionValueStr( VSSADM_O_SNAPSHOT ) != NULL ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_SET_OF_OPTIONS, L"CVssAdminCLI::DeleteSnapshots: Invalid set of options" ); // Figure out snapshot context LONG lSnapshotContext; lSnapshotContext = DetermineSnapshotType( ft, GetOptionValueStr( VSSADM_O_SNAPTYPE ) ); // Calculate the unique volume name, just to make sure that we have the right path WCHAR wszVolumeNameInternal[nLengthOfVolMgmtVolumeName + 1]; // If FOR volume name starts with the '\', assume it is already in the correct volume name format. // This is important for transported volumes since GetVolumeNameForVolumeMountPointW() won't work. if ( GetOptionValueStr( VSSADM_O_FOR )[0] != L'\\' ) { if (!::GetVolumeNameForVolumeMountPointW( GetOptionValueStr( VSSADM_O_FOR ), wszVolumeNameInternal, ARRAY_LEN(wszVolumeNameInternal))) ft.Throw( VSSDBG_COORD, VSS_E_OBJECT_NOT_FOUND, L"GetVolumeNameForVolumeMountPoint(%s,...) " L"failed with error code 0x%08lx", GetOptionValueStr( VSSADM_O_FOR ), ::GetLastError()); } else ::wcsncpy( wszVolumeNameInternal, GetOptionValueStr( VSSADM_O_FOR ), STRING_LEN(wszVolumeNameInternal) ); // Create the coordinator object CComPtr pICoord; ft.LogVssStartupAttempt(); ft.hr = pICoord.CoCreateInstance( CLSID_VSSCoordinator ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); // Set the context ft.hr = pICoord->SetContext( lSnapshotContext ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"SetContext failed with hr = 0x%08lx", ft.hr); // Get list all snapshots CComPtr pIEnumSnapshots; ft.hr = pICoord->Query( GUID_NULL, VSS_OBJECT_NONE, VSS_OBJECT_SNAPSHOT, &pIEnumSnapshots ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Query failed with hr = 0x%08lx", ft.hr); // For all snapshots do... VSS_ID OldestSnapshotId = GUID_NULL; // used if Oldest option is specified VSS_TIMESTAMP OldestSnapshotTimestamp = 0x7FFFFFFFFFFFFFFF; // Used if Oldest option is specified VSS_OBJECT_PROP Prop; VSS_SNAPSHOT_PROP& Snap = Prop.Obj.Snap; // // If not asking to delete the oldest snapshot, this could possibly delete multiple snapshots // Let's determine how many snapshots will be deleted. If one or more, ask the user if we // should continue. If in quiet mode, don't bother the user and skip this step. // if ( !GetOptionValueBool( VSSADM_O_OLDEST ) && !IsQuiet() ) { ULONG ulNumToBeDeleted = 0; for (;;) { ULONG ulFetched; ft.hr = pIEnumSnapshots->Next( 1, &Prop, &ulFetched ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Next failed with hr = 0x%08lx", ft.hr); // Test if the cycle is finished if (ft.hr == S_FALSE) { BS_ASSERT( ulFetched == 0); break; } if ( ::_wcsicmp( Snap.m_pwszOriginalVolumeName, wszVolumeNameInternal ) == 0 ) ++ulNumToBeDeleted; ::VssFreeSnapshotProperties( &Snap ); } if ( ulNumToBeDeleted == 0 ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_NO_ITEMS_IN_QUERY, L"CVssAdminCLI::DeleteSnapshots: No snapshots found that satisfy the query"); if ( !PromptUserForConfirmation( ft, MSG_INFO_PROMPT_USER_FOR_DELETE_SNAPSHOTS, ulNumToBeDeleted ) ) return; // Reset the enumerator to the beginning. ft.hr = pIEnumSnapshots->Reset(); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Reset failed with hr = 0x%08lx", ft.hr); } // // Now iterate through the list of snapshots looking for matches and delete them. // for(;;) { // Get next element ULONG ulFetched; ft.hr = pIEnumSnapshots->Next( 1, &Prop, &ulFetched ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Next failed with hr = 0x%08lx", ft.hr); // Test if the cycle is finished if (ft.hr == S_FALSE) { BS_ASSERT( ulFetched == 0); break; } if ( ::_wcsicmp( Snap.m_pwszOriginalVolumeName, wszVolumeNameInternal ) == 0 ) { // We have a volume name match if ( GetOptionValueBool( VSSADM_O_OLDEST ) ) { // Stow away snapshot info if this is the oldest one so far if ( OldestSnapshotTimestamp > Snap.m_tsCreationTimestamp ) { OldestSnapshotId = Snap.m_SnapshotId; OldestSnapshotTimestamp = Snap.m_tsCreationTimestamp; } } else { // Delete the snapshot VSS_ID NondeletedSnapshotId; LONG lNumDeletedPrivate; ft.hr = pICoord->DeleteSnapshots( Snap.m_SnapshotId, VSS_OBJECT_SNAPSHOT, TRUE, &lNumDeletedPrivate, &NondeletedSnapshotId ); if ( ft.HrFailed() ) { // If it is object not found, the snapshot must have gotten deleted by someone else if ( ft.hr != VSS_E_OBJECT_NOT_FOUND ) { // Print out an error message but keep going LPWSTR pwszSnapshotId = ::GuidToString( ft, Snap.m_SnapshotId ); OutputErrorMsg( ft, MSG_ERROR_UNABLE_TO_DELETE_SNAPSHOT, ft.hr, pwszSnapshotId ); ::VssFreeString( pwszSnapshotId ); } } else { lNumDeleted += lNumDeletedPrivate; } } } ::VssFreeSnapshotProperties( &Snap ); } // If in delete oldest mode, do the delete if ( GetOptionValueBool( VSSADM_O_OLDEST ) && OldestSnapshotId != GUID_NULL ) { if ( PromptUserForConfirmation( ft, MSG_INFO_PROMPT_USER_FOR_DELETE_SNAPSHOTS, 1 ) ) { // Delete the snapshot VSS_ID NondeletedSnapshotId; ft.hr = pICoord->DeleteSnapshots( OldestSnapshotId, VSS_OBJECT_SNAPSHOT, TRUE, &lNumDeleted, &NondeletedSnapshotId ); if ( ft.HrFailed() ) { LPWSTR pwszSnapshotId = ::GuidToString( ft, OldestSnapshotId ); // If it is object not found, the snapshot must have gotten deleted by someone else if ( ft.hr == VSS_E_OBJECT_NOT_FOUND ) { OutputErrorMsg( ft, MSG_ERROR_SNAPSHOT_NOT_FOUND, pwszSnapshotId ); } else { OutputErrorMsg( ft, MSG_ERROR_UNABLE_TO_DELETE_SNAPSHOT, ft.hr, pwszSnapshotId ); } ::VssFreeString( pwszSnapshotId ); } } else return; } if ( lNumDeleted == 0 ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_NO_ITEMS_IN_QUERY, L"CVssAdminCLI::DeleteSnapshots: No snapshots found that satisfy the query"); } if ( !IsQuiet() && lNumDeleted > 0 ) OutputMsg( ft, MSG_INFO_SNAPSHOTS_DELETED_SUCCESSFULLY, lNumDeleted ); m_nReturnValue = VSS_CMDRET_SUCCESS; } void CVssAdminCLI::ExposeSnapshot( IN CVssFunctionTracer& ft ) throw(HRESULT) { BOOL bExposeLocally = TRUE; LPWSTR pwszExposeUsing = GetOptionValueStr( VSSADM_O_EXPOSE_USING ); LPWSTR pwszPathFromRoot = GetOptionValueStr( VSSADM_O_SHAREPATH ); if ( pwszExposeUsing == NULL ) { // Option not given, set it to an empty string pwszExposeUsing = L""; } else if ( ( ::wcslen( pwszExposeUsing ) >= 2 && pwszExposeUsing[1] != L':' ) || ( ::wcslen( pwszExposeUsing ) < 2 ) ) { // User specified a share name bExposeLocally = FALSE; } // See if this is a valid command if ( bExposeLocally && pwszPathFromRoot != NULL ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_SET_OF_OPTIONS, L"CVssAdminCLI::ExposeSnapshot: Specified a ShareUsing option with an expose locally command" ); if ( pwszPathFromRoot == NULL ) pwszPathFromRoot = L""; else if ( pwszPathFromRoot[0] != L'\\' ) ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_OPTION_VALUE, L"CVssAdminCLI::ExposeSnapshot: Specified SharePath doesn't start with '\\'" ); LONG lAttributes; if ( bExposeLocally ) lAttributes = VSS_VOLSNAP_ATTR_EXPOSED_LOCALLY; else lAttributes = VSS_VOLSNAP_ATTR_EXPOSED_REMOTELY; // Create the coordinator object CComPtr pICoord; ft.LogVssStartupAttempt(); ft.hr = pICoord.CoCreateInstance( CLSID_VSSCoordinator ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); VSS_ID SnapshotId = GUID_NULL; if ( !ScanGuid( ft, GetOptionValueStr( VSSADM_O_SNAPSHOT ), SnapshotId ) ) { ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_OPTION_VALUE, L"CVssAdminCLI::ExposeSnapshot: Invalid snapshot id" ); } LPWSTR wszExposedAs = NULL; // Now try to expose ft.hr = pICoord->ExposeSnapshot( SnapshotId, pwszPathFromRoot, lAttributes, pwszExposeUsing, &wszExposedAs ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error returned from ExposeSnapshot: hr = 0x%08x", ft.hr); // The snapshot is exposed, print the results to the user OutputMsg( ft, MSG_INFO_EXPOSE_SNAPSHOT_SUCCESSFUL, wszExposedAs ); ::VssFreeString( wszExposedAs ); m_nReturnValue = VSS_CMDRET_SUCCESS; } // Creating a backup snapshot void CVssAdminCLI::CreateNoWritersSnapshot( IN LONG lSnapshotContext ) throw(HRESULT) { CVssFunctionTracer ft(VSSDBG_VSSADMIN, L"CVssAdminCLI::CreateNoWritersSnapshot"); VSS_ID ProviderId = GUID_NULL; // // If user specified a provider, process option // LPCWSTR pwszProvider = GetOptionValueStr( VSSADM_O_PROVIDER ); if ( pwszProvider != NULL ) { // // Determine if this is an ID or a name // if ( !ScanGuid( ft, pwszProvider, ProviderId ) ) { // Have a provider name, look it up if ( !GetProviderId( ft, pwszProvider, &ProviderId ) ) { // Provider name not found, print error OutputErrorMsg( ft, MSG_ERROR_PROVIDER_NAME_NOT_FOUND, pwszProvider ); return; } } } // Create the coordinator object CComPtr pICoord; ft.LogVssStartupAttempt(); ft.hr = pICoord.CoCreateInstance( CLSID_VSSCoordinator ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Connection failed with hr = 0x%08lx", ft.hr); ft.hr = pICoord->SetContext(lSnapshotContext); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error from SetContext(0x%x) hr = 0x%08lx", lSnapshotContext, ft.hr); CComPtr pAsync; VSS_ID SnapshotSetId = GUID_NULL; // Starting a new snapshot set. Note, if another process is creating snapshots, then // this will fail. If AutoRetry was specified, then retry the start snapshot set for // that the specified number of minutes. LONGLONG llTimeout = 0; if ( GetOptionValueNum( ft, VSSADM_O_AUTORETRY, &llTimeout ) ) { LARGE_INTEGER liPerfCount; (void)QueryPerformanceCounter( &liPerfCount ); ::srand( liPerfCount.LowPart ); DWORD dwTickcountStart = ::GetTickCount(); do { ft.hr = pICoord->StartSnapshotSet(&SnapshotSetId); if ( ft.HrFailed() ) { if ( ft.hr == VSS_E_SNAPSHOT_SET_IN_PROGRESS && ( (LONGLONG)( ::GetTickCount() - dwTickcountStart ) < ( llTimeout * 1000 * 60 ) ) ) { static dwMSec = 250; // Starting retry time if ( dwMSec < 10000 ) { dwMSec += ::rand() % 750; } ft.Trace( VSSDBG_VSSADMIN, L"Snapshot already in progress, retrying in %u millisecs", dwMSec ); Sleep( dwMSec ); } else { ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error from StartSnapshotSet hr = 0x%08lx", ft.hr); } } } while ( ft.HrFailed() ); } else { // // Error right away with out a timeout when there is another snapshot in progress. // ft.hr = pICoord->StartSnapshotSet(&SnapshotSetId); if ( ft.HrFailed() ) { ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error from StartSnapshotSet hr = 0x%08lx", ft.hr); } } // Add the volume to the snapshot set VSS_ID SnapshotId; ft.hr = pICoord->AddToSnapshotSet( GetOptionValueStr( VSSADM_O_FOR ), ProviderId, &SnapshotId); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error from AddToSnapshotSet hr = 0x%08lx", ft.hr); ft.hr = S_OK; pAsync = NULL; ft.hr = pICoord->DoSnapshotSet(NULL, &pAsync); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error from DoSnapshotSet hr = 0x%08lx", ft.hr); ft.hr = pAsync->Wait(); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error from Wait hr = 0x%08lx", ft.hr); HRESULT hrStatus; ft.hr = pAsync->QueryStatus(&hrStatus, NULL); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error from QueryStatus hr = 0x%08lx", ft.hr); // // If VSS failed to create the snapshot, it's result code is in hrStatus. Process // it. // ft.hr = hrStatus; if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"QueryStatus hrStatus parameter returned error, hr = 0x%08lx", ft.hr); // // Print results // VSS_SNAPSHOT_PROP sSSProp; ft.hr = pICoord->GetSnapshotProperties( SnapshotId, &sSSProp ); if ( ft.HrFailed() ) ft.Throw( VSSDBG_VSSADMIN, ft.hr, L"Error from GetId hr = 0x%08lx", ft.hr); LPWSTR pwszSnapshotId = ::GuidToString( ft, SnapshotId ); OutputMsg( ft, MSG_INFO_SNAPSHOT_CREATED, GetOptionValueStr( VSSADM_O_FOR ), pwszSnapshotId, sSSProp.m_pwszSnapshotDeviceObject ); ::VssFreeString(pwszSnapshotId); ::VssFreeSnapshotProperties(&sSSProp); m_nReturnValue = VSS_CMDRET_SUCCESS; } LPWSTR CVssAdminCLI::BuildSnapshotAttributeDisplayString( IN CVssFunctionTracer& ft, IN DWORD Attr ) throw(HRESULT) { WCHAR pwszDisplayString[1024] = L""; WORD wBit = 0; // Go through the bits of the attribute for ( ; wBit < (sizeof ( Attr ) * 8) ; ++wBit ) { switch ( Attr & ( 1 << wBit ) ) { case 0: break; case VSS_VOLSNAP_ATTR_PERSISTENT: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_PERSISTENT, 0, L", " ); break; case VSS_VOLSNAP_ATTR_READ_WRITE: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_READ_WRITE, 0, L", " ); break; case VSS_VOLSNAP_ATTR_CLIENT_ACCESSIBLE: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_CLIENT_ACCESSIBLE, 0, L", " ); break; case VSS_VOLSNAP_ATTR_NO_AUTO_RELEASE: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_NO_AUTO_RELEASE, 0, L", " ); break; case VSS_VOLSNAP_ATTR_NO_WRITERS: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_NO_WRITERS, 0, L", " ); break; case VSS_VOLSNAP_ATTR_TRANSPORTABLE: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_TRANSPORTABLE, 0, L", " ); break; case VSS_VOLSNAP_ATTR_NOT_SURFACED: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_NOT_SURFACED, 0, L", " ); break; case VSS_VOLSNAP_ATTR_HARDWARE_ASSISTED: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_HARDWARE_ASSISTED, 0, L", " ); break; case VSS_VOLSNAP_ATTR_DIFFERENTIAL: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_DIFFERENTIAL, 0, L", " ); break; case VSS_VOLSNAP_ATTR_PLEX: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_PLEX, 0, L", " ); break; case VSS_VOLSNAP_ATTR_IMPORTED: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_IMPORTED, 0, L", " ); break; case VSS_VOLSNAP_ATTR_EXPOSED_LOCALLY: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_EXPOSED_LOCALLY, 0, L", " ); break; case VSS_VOLSNAP_ATTR_EXPOSED_REMOTELY: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_EXPOSED_REMOTELY, 0, L", " ); break; default: AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), 0, Attr & ( 1 << wBit ), L", " ); break; } } // If this is a backup snapshot, most like there will not be any attributes if ( pwszDisplayString[0] == L'\0' ) { AppendMessageToStr( ft, pwszDisplayString, STRING_SIZE( pwszDisplayString ), MSG_INFO_SNAPSHOT_ATTR_NONE, 0, L", " ); } LPWSTR pwszRetString = NULL; ::VssSafeDuplicateStr( ft, pwszRetString, pwszDisplayString ); return pwszRetString; } LONG CVssAdminCLI::DetermineSnapshotType( IN CVssFunctionTracer& ft, IN LPCWSTR pwszType ) throw(HRESULT) { // Determine the snapshot type based on the entered snapshot type string. // See if the snapshot type was specified, if not, return all context if ( pwszType == NULL || pwszType[0] == L'\0' ) { return VSS_CTX_ALL; } INT idx; // Determine type of snapshot for ( idx = 0; g_asAdmTypeNames[idx].pwszName != NULL; ++idx ) { if ( ( CVssSKU::GetSKU() & g_asAdmTypeNames[idx].dwSKUs ) && ( ::_wcsicmp( pwszType, g_asAdmTypeNames[idx].pwszName ) == 0 ) ) { break; } } if ( g_asAdmTypeNames[idx].pwszName == NULL ) { ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_OPTION_VALUE, L"DetermineSnapshotType: Invalid type specified: %s", pwszType ); } // // Now return the context // return( g_asAdmTypeNames[idx].lSnapshotContext ); } LPWSTR CVssAdminCLI::DetermineSnapshotType( IN CVssFunctionTracer& ft, IN LONG lSnapshotAttributes ) throw(HRESULT) { // Determine the snapshot type string based on the snapshot attributes LPWSTR pwszType = NULL; INT idx; // Determine type of snapshot for ( idx = 0; g_asAdmTypeNames[idx].pwszName != NULL; ++idx ) { if ( g_asAdmTypeNames[idx].lSnapshotContext == ( lSnapshotAttributes & VSS_CTX_ATTRIB_MASK ) ) break; } if ( g_asAdmTypeNames[idx].pwszName == NULL ) { ft.Trace( VSSDBG_VSSADMIN, L"DetermineSnapshotType: Invalid context in lSnapshotAttributes: 0x%08x", lSnapshotAttributes ); LPWSTR pwszMsg = GetMsg( ft, FALSE, MSG_UNKNOWN ); if ( pwszMsg == NULL ) { ft.Throw( VSSDBG_VSSADMIN, E_UNEXPECTED, L"Error on loading the message string id %d. 0x%08lx", MSG_UNKNOWN, ::GetLastError() ); } return pwszMsg; } // // Now return the context // ::VssSafeDuplicateStr( ft, pwszType, g_asAdmTypeNames[idx].pwszName ); return pwszType; }