You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1224 lines
35 KiB
1224 lines
35 KiB
/*++
|
|
|
|
Copyright (c) 1999-2001 Microsoft Corporation
|
|
|
|
Abstract:
|
|
|
|
@doc
|
|
@module utility.cpp | Implementation of the Volume Snapshots admin utility
|
|
@end
|
|
|
|
Author:
|
|
|
|
Adi Oltean [aoltean] 09/17/1999
|
|
Stefan Steiner [ssteiner] 03/27/2001
|
|
|
|
TBD:
|
|
|
|
Add comments.
|
|
|
|
Revision History:
|
|
|
|
Name Date Comments
|
|
ssteiner 03/27/2001 Created
|
|
--*/
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Includes
|
|
|
|
// The rest of includes are specified here
|
|
#include "vssadmin.h"
|
|
#include <float.h>
|
|
|
|
#define VSS_LINE_BREAK_COLUMN (79)
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// 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 "ADMUTILC"
|
|
|
|
LPCWSTR CVssAdminCLI::GetVolumeDisplayName(
|
|
IN LPCWSTR pwszVolumeName
|
|
)
|
|
{
|
|
//
|
|
// Note that if something fails in this function, the program can continue
|
|
// to run. It's just that certain output will not show the volume display
|
|
// name along with the volume name
|
|
//
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::GetVolumeDisplayName" );
|
|
|
|
if ( m_pMapVolumeNames == NULL )
|
|
{
|
|
// Create a Coordinator interface
|
|
CComPtr<IVssSnapshotMgmt> pIMgmt;
|
|
|
|
ft.CoCreateInstanceWithLog(
|
|
VSSDBG_VSSADMIN,
|
|
CLSID_VssSnapshotMgmt,
|
|
L"VssSnapshotMgmt",
|
|
CLSCTX_ALL,
|
|
IID_IVssSnapshotMgmt,
|
|
(IUnknown**)&(pIMgmt));
|
|
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<IVssEnumMgmtObject> pIEnumMgmt;
|
|
ft.hr = pIMgmt->QueryVolumesSupportedForSnapshots(
|
|
VSS_SWPRV_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::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;
|
|
m_pMapVolumeNames = new CVssSimpleMap<LPCWSTR, LPCWSTR>;
|
|
if ( m_pMapVolumeNames == NULL )
|
|
ft.Throw( VSSDBG_COORD, E_OUTOFMEMORY, L"Memory allocation error");
|
|
|
|
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;
|
|
}
|
|
|
|
// If it is a simple drive letter it comes back like V:\, change to V:
|
|
if ( ::wcslen( VolProp.m_pwszVolumeDisplayName ) == 3 && VolProp.m_pwszVolumeDisplayName[2] == L'\\' )
|
|
VolProp.m_pwszVolumeDisplayName[2] = L'\0';
|
|
|
|
// Add it to the map. Note that the strings in the volume property struct are transferred to the
|
|
// map.
|
|
if ( !m_pMapVolumeNames->Add( VolProp.m_pwszVolumeName, VolProp.m_pwszVolumeDisplayName ) )
|
|
{
|
|
::VssFreeString( VolProp.m_pwszVolumeName );
|
|
::VssFreeString( VolProp.m_pwszVolumeDisplayName );
|
|
ft.Throw( VSSDBG_COORD, E_OUTOFMEMORY, L"Memory allocation error");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns NULL if the volume name is not found
|
|
return m_pMapVolumeNames->Lookup(pwszVolumeName);
|
|
}
|
|
|
|
|
|
LPCWSTR CVssAdminCLI::LoadString(
|
|
IN UINT uStringId
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::LoadString" );
|
|
|
|
LPCWSTR wszReturnedString = m_mapCachedResourceStrings.Lookup(uStringId);
|
|
if (wszReturnedString)
|
|
return wszReturnedString;
|
|
|
|
// Load the string from resources.
|
|
WCHAR wszBuffer[x_nStringBufferSize];
|
|
INT nReturnedCharacters = ::LoadStringW(
|
|
GetModuleHandle(NULL),
|
|
uStringId,
|
|
wszBuffer,
|
|
x_nStringBufferSize - 1
|
|
);
|
|
if (nReturnedCharacters == 0)
|
|
ft.Throw( VSSDBG_VSSADMIN, E_UNEXPECTED,
|
|
L"Error on loading the string %u. 0x%08lx",
|
|
uStringId, ::GetLastError() );
|
|
|
|
// Duplicate the new string
|
|
LPWSTR wszNewString = NULL;
|
|
::VssSafeDuplicateStr( ft, wszNewString, wszBuffer );
|
|
wszReturnedString = wszNewString;
|
|
|
|
// Save the string in the cache
|
|
if ( !m_mapCachedResourceStrings.Add( uStringId, wszReturnedString ) ) {
|
|
::VssFreeString( wszReturnedString );
|
|
ft.Throw( VSSDBG_COORD, E_OUTOFMEMORY, L"Memory allocation error");
|
|
}
|
|
|
|
return wszReturnedString;
|
|
}
|
|
|
|
|
|
LPCWSTR CVssAdminCLI::GetNextCmdlineToken(
|
|
IN bool bFirstToken /* = false */
|
|
) throw(HRESULT)
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
This function returns the tokens in the command line.
|
|
|
|
The function will skip any separators (space and tab).
|
|
|
|
If bFirstCall == true then it will return the first token.
|
|
Otherwise subsequent calls will return subsequent tokens.
|
|
|
|
If the last token is NULL then there are no more tokens in the command line.
|
|
|
|
--*/
|
|
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::GetNextCmdlineToken" );
|
|
|
|
static INT iCurrArgc;
|
|
WCHAR *pwsz;
|
|
|
|
if ( bFirstToken )
|
|
iCurrArgc = 0;
|
|
|
|
if ( iCurrArgc >= m_argc )
|
|
return NULL;
|
|
|
|
pwsz = m_argv[iCurrArgc++];
|
|
|
|
return pwsz;
|
|
}
|
|
|
|
|
|
bool CVssAdminCLI::Match(
|
|
IN LPCWSTR wszString,
|
|
IN LPCWSTR wszPatternString
|
|
) throw(HRESULT)
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
This function returns true iif the given string matches the
|
|
pattern string. The comparison is case insensitive.
|
|
|
|
--*/
|
|
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::Match" );
|
|
|
|
// If the string is NULL then the Match failed.
|
|
if (wszString == NULL) return false;
|
|
|
|
// Check for string equality (case insensitive)
|
|
return (::_wcsicmp( wszString, wszPatternString ) == 0);
|
|
}
|
|
|
|
|
|
bool CVssAdminCLI::ScanGuid(
|
|
IN LPCWSTR wszString,
|
|
OUT VSS_ID& Guid
|
|
) throw(HRESULT)
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
This function returns true iif the given string matches a guid.
|
|
The guid is returned in the proper variable.
|
|
The formatting is case insensitive.
|
|
|
|
--*/
|
|
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::ScanGuid" );
|
|
|
|
return SUCCEEDED(::CLSIDFromString(W2OLE(const_cast<WCHAR*>(wszString)), &Guid));
|
|
}
|
|
|
|
|
|
void CVssAdminCLI::Output(
|
|
IN LPCWSTR wszFormat,
|
|
...
|
|
) throw(HRESULT)
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
This function returns true if the given string matches the
|
|
pattern strig from resources. The comparison is case insensitive.
|
|
|
|
--*/
|
|
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::Output" );
|
|
|
|
WCHAR wszOutputBuffer[x_nStringBufferSize];
|
|
|
|
// Format the final string
|
|
va_list marker;
|
|
va_start( marker, wszFormat );
|
|
StringCchVPrintfW( STRING_CCH_PARAM(wszOutputBuffer), wszFormat, marker );
|
|
va_end( marker );
|
|
|
|
// Print the final string to the output
|
|
OutputOnConsole( wszOutputBuffer );
|
|
}
|
|
|
|
|
|
void CVssAdminCLI::OutputMsg(
|
|
IN LONG msgId,
|
|
...
|
|
)
|
|
/*++
|
|
|
|
Description:
|
|
|
|
This function outputs a msg.mc message.
|
|
|
|
--*/
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::OutputMsg" );
|
|
|
|
va_list args;
|
|
LPWSTR lpMsgBuf;
|
|
|
|
va_start( args, msgId );
|
|
|
|
if (::FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE,
|
|
NULL,
|
|
msgId,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPWSTR) &lpMsgBuf,
|
|
0,
|
|
&args
|
|
))
|
|
{
|
|
OutputOnConsole( lpMsgBuf );
|
|
::LocalFree( lpMsgBuf );
|
|
}
|
|
else
|
|
{
|
|
if (::FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL,
|
|
msgId,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPWSTR) &lpMsgBuf,
|
|
0,
|
|
&args))
|
|
{
|
|
OutputOnConsole( lpMsgBuf );
|
|
::LocalFree( lpMsgBuf );
|
|
}
|
|
else
|
|
{
|
|
::wprintf( L"Unable to format message for id %x - %d\n", msgId, ::GetLastError( ));
|
|
}
|
|
}
|
|
va_end( args );
|
|
}
|
|
|
|
LPWSTR CVssAdminCLI::GetMsg(
|
|
IN BOOL bLineBreaks,
|
|
IN LONG msgId,
|
|
...
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::GetMsg" );
|
|
|
|
va_list args;
|
|
LPWSTR lpMsgBuf;
|
|
LPWSTR lpReturnStr = NULL;
|
|
|
|
va_start( args, msgId );
|
|
|
|
if (::FormatMessageW(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
|
|
( bLineBreaks ? VSS_LINE_BREAK_COLUMN : FORMAT_MESSAGE_MAX_WIDTH_MASK ),
|
|
NULL,
|
|
msgId,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPWSTR) &lpMsgBuf,
|
|
0,
|
|
&args
|
|
))
|
|
{
|
|
::VssSafeDuplicateStr( ft, lpReturnStr, lpMsgBuf );
|
|
::LocalFree( lpMsgBuf );
|
|
}
|
|
else if (::FormatMessageW(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
( bLineBreaks ? VSS_LINE_BREAK_COLUMN : FORMAT_MESSAGE_MAX_WIDTH_MASK ),
|
|
NULL,
|
|
msgId,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPWSTR) &lpMsgBuf,
|
|
0,
|
|
&args ) )
|
|
{
|
|
::VssSafeDuplicateStr( ft, lpReturnStr, lpMsgBuf );
|
|
::LocalFree( lpMsgBuf );
|
|
}
|
|
|
|
va_end( args );
|
|
|
|
// Returns NULL if message was not found
|
|
return lpReturnStr;
|
|
}
|
|
|
|
void CVssAdminCLI::AppendMessageToStr(
|
|
IN LPWSTR pwszString,
|
|
IN SIZE_T cMaxStrLen,
|
|
IN LONG lMsgId,
|
|
IN DWORD AttrBit,
|
|
IN LPCWSTR pwszDelimitStr
|
|
) throw( HRESULT )
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::AppendMessageToStr" );
|
|
|
|
size_t cMaxAppendChars = cMaxStrLen - wcslen(pwszString);
|
|
|
|
if ( pwszString[0] != L'\0' )
|
|
{
|
|
// First append the delimiter
|
|
::wcsncat( pwszString, pwszDelimitStr, cMaxAppendChars );
|
|
cMaxAppendChars = cMaxStrLen - wcslen(pwszString);
|
|
}
|
|
|
|
// If this is a known message, lMsgId != 0
|
|
if ( lMsgId != 0 )
|
|
{
|
|
LPWSTR pwszMsg;
|
|
pwszMsg = GetMsg( FALSE, lMsgId );
|
|
if ( pwszMsg == NULL )
|
|
{
|
|
ft.Throw( VSSDBG_VSSADMIN, E_UNEXPECTED,
|
|
L"Error on loading the message string id %d. 0x%08lx",
|
|
lMsgId, ::GetLastError() );
|
|
}
|
|
|
|
::wcsncat( pwszString, pwszMsg, cMaxAppendChars );
|
|
cMaxAppendChars = cMaxStrLen - wcslen(pwszString);
|
|
::VssFreeString( pwszMsg );
|
|
}
|
|
else
|
|
{
|
|
// No message for this one, just append the attribute in hex
|
|
WCHAR pwszBitStr[64];
|
|
StringCchPrintfW( STRING_CCH_PARAM(pwszBitStr),
|
|
L"0x%x", AttrBit
|
|
);
|
|
|
|
::wcsncat( pwszString, pwszBitStr, cMaxAppendChars);
|
|
cMaxAppendChars = cMaxStrLen - wcslen(pwszString);
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Scans a number input by the user and converts it to a LONGLONG. Accepts the following
|
|
// unit suffixes: B, K, KB, M, MB, G, GB, T, TB, P, PB, E, EB and floating point.
|
|
//
|
|
LONGLONG CVssAdminCLI::ScanNumber(
|
|
IN LPCWSTR pwszNumToConvert,
|
|
IN BOOL bSuffixAllowed
|
|
) throw( HRESULT )
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::ScanNumber" );
|
|
|
|
WCHAR wUnit = L'B';
|
|
SIZE_T NumStrLen;
|
|
|
|
// If the string is NULL, assume infinite size
|
|
if ( pwszNumToConvert == NULL )
|
|
return -1;
|
|
|
|
//
|
|
// Set up an automatically released temporary string
|
|
//
|
|
CVssAutoPWSZ autoStrNum;
|
|
autoStrNum.CopyFrom( pwszNumToConvert );
|
|
LPWSTR pwszNum = autoStrNum.GetRef();
|
|
|
|
NumStrLen = ::wcslen( pwszNum );
|
|
|
|
// Remove trailing spaces
|
|
while ( NumStrLen > 0 && pwszNum[ NumStrLen - 1 ] == L' ' )
|
|
{
|
|
NumStrLen -= 1;
|
|
pwszNum[ NumStrLen ] = L'\0';
|
|
}
|
|
|
|
// If the string is empty, assume infinite size
|
|
if ( NumStrLen == 0 )
|
|
return -1;
|
|
|
|
if ( bSuffixAllowed )
|
|
{
|
|
// See if there is a suffix with three or more alpha chars, if so, error out.
|
|
if ( NumStrLen > 3 && iswalpha( pwszNum[ NumStrLen - 3 ] ) && iswalpha( pwszNum[ NumStrLen - 2 ] ) &&
|
|
iswalpha( pwszNum[ NumStrLen - 1 ] ) )
|
|
{
|
|
ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_NUMBER,
|
|
L"Invalid input number: '%s', too many alpha chars in suffix.", pwszNumToConvert );
|
|
}
|
|
// Now see if there is a single or double byte alpha suffix. If so, put the suffix in wUnit.
|
|
else if ( NumStrLen > 2 && iswalpha( pwszNum[ NumStrLen - 2 ] ) && ( towupper( pwszNum[ NumStrLen - 1 ] ) == L'B' ) )
|
|
{
|
|
if ( towupper( pwszNum[ NumStrLen - 2 ] ) == L'B' )
|
|
ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_NUMBER,
|
|
L"Invalid input number: '%s', BB is not a valid suffix", pwszNumToConvert );
|
|
|
|
wUnit = pwszNum[ NumStrLen - 2 ];
|
|
pwszNum[ NumStrLen - 2 ] = L'\0';
|
|
NumStrLen -= 2;
|
|
}
|
|
else if ( NumStrLen > 1 && iswalpha( pwszNum[ NumStrLen - 1 ] ) )
|
|
{
|
|
wUnit = pwszNum[ NumStrLen - 1 ];
|
|
pwszNum[ NumStrLen - 1 ] = L'\0';
|
|
NumStrLen -= 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Let's make sure the string is only filled with digits...
|
|
SIZE_T cStr = ::wcslen( pwszNum );
|
|
for ( SIZE_T i = 0; i < cStr; ++i )
|
|
{
|
|
if ( ! iswdigit( pwszNum[ i ] ) )
|
|
ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_NUMBER,
|
|
L"Invalid input number: '%s', number must be all digits", pwszNumToConvert );
|
|
}
|
|
}
|
|
|
|
// At this point, the rest of the string should be a valid floating point number
|
|
double dSize;
|
|
if ( swscanf( pwszNum, L"%lf", &dSize ) != 1 )
|
|
ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_NUMBER,
|
|
L"Invalid input number: %s", pwszNumToConvert );
|
|
// Now bump up the size based on suffix
|
|
|
|
switch( towupper( wUnit ) )
|
|
{
|
|
case L'B':
|
|
break;
|
|
case L'E':
|
|
dSize *= 1024.;
|
|
case L'P':
|
|
dSize *= 1024.;
|
|
case L'T':
|
|
dSize *= 1024.;
|
|
case L'G':
|
|
dSize *= 1024.;
|
|
case L'M':
|
|
dSize *= 1024.;
|
|
case L'K':
|
|
dSize *= 1024.;
|
|
break;
|
|
default:
|
|
ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_NUMBER,
|
|
L"Invalid input number: %s", pwszNumToConvert );
|
|
break;
|
|
}
|
|
|
|
LONGLONG llRetVal;
|
|
llRetVal = (LONGLONG)dSize;
|
|
if ( llRetVal <= -1 )
|
|
ft.Throw( VSSDBG_VSSADMIN, VSSADM_E_INVALID_NUMBER,
|
|
L"Invalid input number: %s", pwszNumToConvert );
|
|
|
|
return llRetVal;
|
|
}
|
|
|
|
//
|
|
// Format a LONGLONG into the appropriate string using xB (KB, MB, GB, TB, PB, EB) suffixes.
|
|
// Must use ::VssFreeString() to free returned string.
|
|
//
|
|
LPWSTR CVssAdminCLI::FormatNumber(
|
|
IN LONGLONG llNum
|
|
) throw( HRESULT )
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::FormatNumber" );
|
|
|
|
// If the string is -1 or less, assume this is infinite.
|
|
if ( llNum < 0 )
|
|
{
|
|
LPWSTR pwszMsg = GetMsg( FALSE, MSG_INFINITE );
|
|
if ( pwszMsg == NULL )
|
|
{
|
|
ft.Throw( VSSDBG_VSSADMIN, E_UNEXPECTED,
|
|
L"Error on loading the message string id %d. 0x%08lx",
|
|
MSG_INFINITE, ::GetLastError() );
|
|
}
|
|
|
|
return pwszMsg;
|
|
}
|
|
|
|
// Now convert the size into string
|
|
UINT nExaBytes = (UINT)((llNum >> 60));
|
|
UINT nPetaBytes = (UINT)((llNum >> 50) & 0x3ff);
|
|
UINT nTerraBytes = (UINT)((llNum >> 40) & 0x3ff);
|
|
UINT nGigaBytes = (UINT)((llNum >> 30) & 0x3ff);
|
|
UINT nMegaBytes = (UINT)((llNum >> 20) & 0x3ff);
|
|
UINT nKiloBytes = (UINT)((llNum >> 10) & 0x3ff);
|
|
UINT nBytes = (UINT)( llNum & 0x3ff);
|
|
|
|
LPCWSTR pwszUnit;
|
|
double dSize = 0.0;
|
|
|
|
// Display only biggest units, and never more than 999
|
|
// instead of "1001 KB" we display "0.98 MB"
|
|
if ( (nExaBytes) > 0 || (nPetaBytes > 999) )
|
|
{
|
|
pwszUnit = L"EB";
|
|
dSize = ((double)llNum / (double)( 1 << 30 )) / (double)( 1 << 30 ) ;
|
|
}
|
|
else if ( (nPetaBytes) > 0 || (nTerraBytes > 999) )
|
|
{
|
|
pwszUnit = L"PB";
|
|
dSize = ((double)llNum / (double)( 1 << 30 )) / (double)( 1 << 20 ) ;
|
|
}
|
|
else if ( (nTerraBytes) > 0 || (nGigaBytes > 999) )
|
|
{
|
|
pwszUnit = L"TB";
|
|
dSize = ((double)llNum / (double)( 1 << 30 )) / (double)( 1 << 10 ) ;
|
|
}
|
|
else if ( (nGigaBytes) > 0 || (nMegaBytes > 999) )
|
|
{
|
|
pwszUnit = L"GB";
|
|
dSize = (double)llNum / (double)( 1 << 30 ) ;
|
|
}
|
|
else if ( (nMegaBytes) > 0 || (nKiloBytes > 999) )
|
|
{
|
|
pwszUnit = L"MB";
|
|
dSize = (double)llNum / (double)( 1 << 20 ) ;
|
|
}
|
|
else if ( (nKiloBytes) > 0 || (nBytes > 999) )
|
|
{
|
|
pwszUnit = L"KB";
|
|
dSize = (double)llNum / (double)( 1 << 10 ) ;
|
|
}
|
|
else
|
|
{
|
|
pwszUnit = L"B";
|
|
dSize = (double)nBytes;
|
|
}
|
|
|
|
// BUG 453314: Trunc used instead of Round
|
|
// For explanation of the workaround look for the following KB Article:
|
|
// Q184234: PRB: printf() and _fcvt() Might Produce Incorrect Rounding
|
|
double dRoundedSize = dSize + 1e-10;
|
|
|
|
// Format with op to three decimal points
|
|
WCHAR pwszSize[64];
|
|
StringCchPrintfW( STRING_CCH_PARAM(pwszSize),
|
|
L"%.3f", dRoundedSize
|
|
);
|
|
|
|
SIZE_T len = ::wcslen( pwszSize );
|
|
|
|
// Truncate trailing zeros
|
|
while ( len > 0 && pwszSize[ len - 1 ] == L'0' )
|
|
{
|
|
len -= 1;
|
|
pwszSize[ len ] = L'\0';
|
|
}
|
|
// Truncate trailing decimal point
|
|
if ( len > 0 && pwszSize[ len - 1 ] == L'.' )
|
|
pwszSize[ len - 1 ] = L'\0';
|
|
|
|
// Now attach the unit suffix
|
|
::wcscat( pwszSize, L" " );
|
|
::wcscat( pwszSize, pwszUnit );
|
|
|
|
// Allocate a string buffer and return it.
|
|
LPWSTR pwszRetStr = NULL;
|
|
::VssSafeDuplicateStr( ft, pwszRetStr, pwszSize );
|
|
|
|
return pwszRetStr;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
This function outputs a msg.mc message.
|
|
|
|
--*/
|
|
void CVssAdminCLI::OutputErrorMsg(
|
|
IN LONG msgId,
|
|
...
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::OutputErrorMsg" );
|
|
|
|
va_list args;
|
|
LPWSTR lpMsgBuf;
|
|
|
|
va_start( args, msgId );
|
|
|
|
if (::FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE,
|
|
NULL,
|
|
MSG_ERROR,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPWSTR) &lpMsgBuf,
|
|
0,
|
|
NULL
|
|
))
|
|
{
|
|
OutputOnConsole( lpMsgBuf );
|
|
::LocalFree( lpMsgBuf );
|
|
}
|
|
|
|
if (::FormatMessage(
|
|
(msgId >= MSG_FIRST_MESSAGE_ID ? FORMAT_MESSAGE_FROM_HMODULE :
|
|
FORMAT_MESSAGE_FROM_SYSTEM)
|
|
| FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
NULL,
|
|
msgId,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPWSTR) &lpMsgBuf,
|
|
0,
|
|
&args
|
|
))
|
|
{
|
|
OutputOnConsole( L" " );
|
|
OutputOnConsole( lpMsgBuf );
|
|
OutputOnConsole( L" \r\n" );
|
|
::LocalFree( lpMsgBuf );
|
|
}
|
|
else
|
|
{
|
|
if (::FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL,
|
|
msgId,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPWSTR) &lpMsgBuf,
|
|
0,
|
|
&args))
|
|
{
|
|
OutputOnConsole( L" " );
|
|
OutputOnConsole( lpMsgBuf );
|
|
OutputOnConsole( L" \r\n" );
|
|
::LocalFree( lpMsgBuf );
|
|
}
|
|
else
|
|
{
|
|
::wprintf( L"Unable to format message for id %x - %d\n", msgId, ::GetLastError( ));
|
|
ft.Throw( VSSDBG_VSSADMIN, E_UNEXPECTED,
|
|
L"Error on loading the message string id %d. 0x%08lx",
|
|
msgId, ::GetLastError() );
|
|
}
|
|
}
|
|
|
|
va_end( args );
|
|
}
|
|
|
|
BOOL CVssAdminCLI::PromptUserForConfirmation(
|
|
IN LONG lPromptMsgId,
|
|
IN ULONG ulNum
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::PromptUserForConfirmation" );
|
|
|
|
BOOL bRetVal = FALSE;
|
|
|
|
//
|
|
// First check to see if in quiet mode. If so, simply return
|
|
// true
|
|
//
|
|
if ( GetOptionValueBool( VSSADM_O_QUIET ) )
|
|
return TRUE;
|
|
|
|
//
|
|
// Load the response message string, in English it is "YN"
|
|
//
|
|
LPWSTR pwszResponse;
|
|
pwszResponse = GetMsg( FALSE, MSG_YESNO_RESPONSE_DATA );
|
|
if ( pwszResponse == NULL )
|
|
{
|
|
ft.Throw( VSSDBG_VSSADMIN, E_UNEXPECTED,
|
|
L"Error on loading the message string id %d. 0x%08lx",
|
|
MSG_YESNO_RESPONSE_DATA, ::GetLastError() );
|
|
}
|
|
|
|
//
|
|
// Now prompt the user, note, parameter 4 is the affirmative response (Y) and
|
|
// parameter 5 is the negative reponse (N)
|
|
//
|
|
OutputMsg( lPromptMsgId, ulNum, pwszResponse[0], pwszResponse[1] );
|
|
WCHAR wcIn;
|
|
DWORD fdwMode;
|
|
|
|
// Make sure we are outputting to a real console.
|
|
if ( ( ::GetFileType( m_hConsoleOutput ) & FILE_TYPE_CHAR ) &&
|
|
::GetConsoleMode( m_hConsoleOutput, &fdwMode ) )
|
|
{
|
|
// Going to the console, ask the user.
|
|
wcIn = ::MyGetChar();
|
|
}
|
|
else
|
|
{
|
|
// Output redirected, assume NO
|
|
wcIn = pwszResponse[1]; // N
|
|
}
|
|
|
|
WCHAR wcsOutput[16];
|
|
StringCchPrintfW( STRING_CCH_PARAM(wcsOutput),
|
|
L"%c\n\n", wcIn
|
|
);
|
|
|
|
OutputOnConsole( wcsOutput );
|
|
|
|
//
|
|
// Compare the character using the proper W32 function and
|
|
// not towupper().
|
|
//
|
|
if ( ::CompareStringW( LOCALE_INVARIANT,
|
|
NORM_IGNORECASE | NORM_IGNOREKANATYPE,
|
|
&wcIn,
|
|
1,
|
|
pwszResponse + 0, // Y
|
|
1 ) == CSTR_EQUAL )
|
|
{
|
|
bRetVal = TRUE;
|
|
}
|
|
else
|
|
{
|
|
bRetVal = FALSE;
|
|
}
|
|
|
|
::CoTaskMemFree( pwszResponse );
|
|
return bRetVal;
|
|
}
|
|
|
|
void CVssAdminCLI::OutputOnConsole(
|
|
IN LPCWSTR wszStr
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"CVssAdminCLI::OutputOnConsole" );
|
|
|
|
DWORD dwCharsOutput;
|
|
DWORD fdwMode;
|
|
static BOOL bFirstTime = TRUE;
|
|
static BOOL bIsTrueConsoleOutput;
|
|
|
|
if ( m_hConsoleOutput == INVALID_HANDLE_VALUE )
|
|
{
|
|
throw E_UNEXPECTED;
|
|
}
|
|
|
|
if ( bFirstTime )
|
|
{
|
|
//
|
|
// Stash away the results in static vars. bIsTrueConsoleOutput is TRUE when the
|
|
// standard output handle is pointing to a console character device.
|
|
//
|
|
bIsTrueConsoleOutput = ( ::GetFileType( m_hConsoleOutput ) & FILE_TYPE_CHAR ) &&
|
|
::GetConsoleMode( m_hConsoleOutput, &fdwMode );
|
|
bFirstTime = FALSE;
|
|
}
|
|
|
|
if ( bIsTrueConsoleOutput )
|
|
{
|
|
//
|
|
// Output to the console
|
|
//
|
|
if ( !::WriteConsoleW( m_hConsoleOutput,
|
|
( PVOID )wszStr,
|
|
( DWORD )::wcslen( wszStr ),
|
|
&dwCharsOutput,
|
|
NULL ) )
|
|
{
|
|
throw HRESULT_FROM_WIN32( ::GetLastError() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Output being redirected. WriteConsoleW doesn't work for redirected output. Convert
|
|
// UNICODE to the current output CP multibyte charset.
|
|
//
|
|
LPSTR lpszTmpBuffer;
|
|
DWORD dwByteCount;
|
|
|
|
//
|
|
// Get size of temp buffer needed for the conversion.
|
|
//
|
|
dwByteCount = ::WideCharToMultiByte(
|
|
::GetConsoleOutputCP(),
|
|
0,
|
|
wszStr,
|
|
-1,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
if ( dwByteCount == 0 )
|
|
{
|
|
throw HRESULT_FROM_WIN32( ::GetLastError() );
|
|
}
|
|
|
|
lpszTmpBuffer = ( LPSTR )::malloc( dwByteCount );
|
|
if ( lpszTmpBuffer == NULL )
|
|
{
|
|
throw E_OUTOFMEMORY;
|
|
}
|
|
|
|
//
|
|
// Now convert it.
|
|
//
|
|
dwByteCount = ::WideCharToMultiByte(
|
|
::GetConsoleOutputCP(),
|
|
0,
|
|
wszStr,
|
|
-1,
|
|
lpszTmpBuffer,
|
|
dwByteCount,
|
|
NULL,
|
|
NULL
|
|
);
|
|
if ( dwByteCount == 0 )
|
|
{
|
|
::free( lpszTmpBuffer );
|
|
throw HRESULT_FROM_WIN32( ::GetLastError() );
|
|
}
|
|
|
|
// Finally output it.
|
|
if ( !::WriteFile(
|
|
m_hConsoleOutput,
|
|
lpszTmpBuffer,
|
|
dwByteCount - 1, // Get rid of the trailing NULL char
|
|
&dwCharsOutput,
|
|
NULL ) )
|
|
{
|
|
throw HRESULT_FROM_WIN32( ::GetLastError() );
|
|
}
|
|
|
|
::free( lpszTmpBuffer );
|
|
}
|
|
}
|
|
|
|
BOOL CVssAdminCLI::UnloggableError(IN HRESULT hError)
|
|
{
|
|
switch (hError)
|
|
{
|
|
case VSSADM_E_INVALID_NUMBER:
|
|
case VSSADM_E_INVALID_COMMAND:
|
|
case VSSADM_E_INVALID_OPTION:
|
|
case VSSADM_E_INVALID_OPTION_VALUE:
|
|
case VSSADM_E_DUPLICATE_OPTION:
|
|
case VSSADM_E_OPTION_NOT_ALLOWED_FOR_COMMAND:
|
|
case VSSADM_E_REQUIRED_OPTION_MISSING:
|
|
case VSSADM_E_INVALID_SET_OF_OPTIONS:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Returns TRUE if error message was mapped
|
|
//
|
|
BOOL MapVssErrorToMsg(
|
|
IN HRESULT hr,
|
|
OUT LONG *plMsgNum
|
|
) throw( HRESULT )
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"MapVssErrorToMsg" );
|
|
|
|
ft.Trace( VSSDBG_VSSADMIN, L"Input HR: 0x%08x", hr );
|
|
|
|
LONG msg = 0;
|
|
*plMsgNum = 0;
|
|
|
|
switch ( hr )
|
|
{
|
|
case VSSADM_E_NO_ITEMS_IN_QUERY:
|
|
msg = MSG_ERROR_NO_ITEMS_FOUND;
|
|
break;
|
|
// Parsing errors:
|
|
case VSSADM_E_INVALID_NUMBER:
|
|
msg = MSG_ERROR_INVALID_INPUT_NUMBER;
|
|
break;
|
|
case VSSADM_E_INVALID_COMMAND:
|
|
msg = MSG_ERROR_INVALID_COMMAND;
|
|
break;
|
|
case VSSADM_E_INVALID_OPTION:
|
|
msg = MSG_ERROR_INVALID_OPTION;
|
|
break;
|
|
case E_INVALIDARG:
|
|
case VSSADM_E_INVALID_OPTION_VALUE:
|
|
msg = MSG_ERROR_INVALID_OPTION_VALUE;
|
|
break;
|
|
case VSSADM_E_DUPLICATE_OPTION:
|
|
msg = MSG_ERROR_DUPLICATE_OPTION;
|
|
break;
|
|
case VSSADM_E_OPTION_NOT_ALLOWED_FOR_COMMAND:
|
|
msg = MSG_ERROR_OPTION_NOT_ALLOWED_FOR_COMMAND;
|
|
break;
|
|
case VSSADM_E_REQUIRED_OPTION_MISSING:
|
|
msg = MSG_ERROR_REQUIRED_OPTION_MISSING;
|
|
break;
|
|
case VSSADM_E_INVALID_SET_OF_OPTIONS:
|
|
msg = MSG_ERROR_INVALID_SET_OF_OPTIONS;
|
|
break;
|
|
case VSSADM_E_SNAPSHOT_NOT_FOUND:
|
|
msg = MSG_ERROR_SNAPSHOT_NOT_FOUND2;
|
|
break;
|
|
case VSSADM_E_DELETION_DENIED:
|
|
msg = MSG_ERROR_DELETION_DENIED;
|
|
break;
|
|
// VSS errors
|
|
case VSS_E_PROVIDER_NOT_REGISTERED:
|
|
msg = MSG_ERROR_VSS_PROVIDER_NOT_REGISTERED;
|
|
break;
|
|
case VSS_E_OBJECT_NOT_FOUND:
|
|
msg = MSG_ERROR_VSS_VOLUME_NOT_FOUND;
|
|
break;
|
|
case VSS_E_PROVIDER_VETO:
|
|
msg = MSG_ERROR_VSS_PROVIDER_VETO;
|
|
break;
|
|
case VSS_E_VOLUME_NOT_SUPPORTED:
|
|
msg = MSG_ERROR_VSS_VOLUME_NOT_SUPPORTED;
|
|
break;
|
|
case VSS_E_VOLUME_NOT_SUPPORTED_BY_PROVIDER:
|
|
msg = MSG_ERROR_VSS_VOLUME_NOT_SUPPORTED_BY_PROVIDER;
|
|
break;
|
|
case VSS_E_UNEXPECTED_PROVIDER_ERROR:
|
|
msg = MSG_ERROR_VSS_UNEXPECTED_PROVIDER_ERROR;
|
|
break;
|
|
case VSS_E_FLUSH_WRITES_TIMEOUT:
|
|
msg = MSG_ERROR_VSS_FLUSH_WRITES_TIMEOUT;
|
|
break;
|
|
case VSS_E_HOLD_WRITES_TIMEOUT:
|
|
msg = MSG_ERROR_VSS_HOLD_WRITES_TIMEOUT;
|
|
break;
|
|
case VSS_E_UNEXPECTED_WRITER_ERROR:
|
|
msg = MSG_ERROR_VSS_UNEXPECTED_WRITER_ERROR;
|
|
break;
|
|
case VSS_E_SNAPSHOT_SET_IN_PROGRESS:
|
|
msg = MSG_ERROR_VSS_SNAPSHOT_SET_IN_PROGRESS;
|
|
break;
|
|
case VSS_E_MAXIMUM_NUMBER_OF_SNAPSHOTS_REACHED:
|
|
msg = MSG_ERROR_VSS_MAXIMUM_NUMBER_OF_SNAPSHOTS_REACHED;
|
|
break;
|
|
case VSS_E_UNSUPPORTED_CONTEXT:
|
|
msg = MSG_ERROR_VSS_UNSUPPORTED_CONTEXT;
|
|
break;
|
|
case VSS_E_MAXIMUM_DIFFAREA_ASSOCIATIONS_REACHED:
|
|
msg = MSG_ERROR_VSS_MAXIMUM_DIFFAREA_ASSOCIATIONS_REACHED;
|
|
break;
|
|
case VSS_E_INSUFFICIENT_STORAGE:
|
|
msg = MSG_ERROR_VSS_INSUFFICIENT_STORAGE;
|
|
break;
|
|
|
|
case E_OUTOFMEMORY:
|
|
msg = MSG_ERROR_OUT_OF_MEMORY;
|
|
break;
|
|
case E_ACCESSDENIED:
|
|
msg = MSG_ERROR_ACCESS_DENIED;
|
|
break;
|
|
|
|
case VSS_E_BAD_STATE:
|
|
case VSS_E_CORRUPT_XML_DOCUMENT:
|
|
case VSS_E_INVALID_XML_DOCUMENT:
|
|
case VSS_E_MAXIMUM_NUMBER_OF_VOLUMES_REACHED:
|
|
msg = MSG_ERROR_INTERNAL_VSSADMIN_ERROR;
|
|
break;
|
|
}
|
|
|
|
if ( msg == 0 )
|
|
return FALSE;
|
|
|
|
*plMsgNum = msg;
|
|
|
|
ft.Trace( VSSDBG_VSSADMIN, L"Output Msg#: 0x%08x", msg );
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
LPWSTR GuidToString(
|
|
IN GUID guid
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"GuidToString" );
|
|
|
|
LPWSTR pwszGuid;
|
|
|
|
// {36e4be76-035d-11d5-9ef2-806d6172696f}
|
|
const x_MaxGuidSize = 40;
|
|
pwszGuid = (LPWSTR)::CoTaskMemAlloc( x_MaxGuidSize * sizeof( WCHAR ) );
|
|
if ( pwszGuid == NULL )
|
|
ft.Throw( VSSDBG_VSSADMIN, E_OUTOFMEMORY,
|
|
L"Error from CoTaskMemAlloc: 0x%08lx",
|
|
::GetLastError() );
|
|
|
|
StringCchPrintfW( pwszGuid, x_MaxGuidSize,
|
|
WSTR_GUID_FMT, GUID_PRINTF_ARG( guid )
|
|
);
|
|
|
|
return pwszGuid;
|
|
}
|
|
|
|
|
|
LPWSTR DateTimeToString(
|
|
IN VSS_TIMESTAMP *pTimeStamp
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"DateTimeToString" );
|
|
|
|
LPWSTR pwszDateTime;
|
|
SYSTEMTIME stLocal;
|
|
FILETIME ftLocal;
|
|
WCHAR pwszDate[ 64 ];
|
|
WCHAR pwszTime[ 64 ];
|
|
|
|
if ( pTimeStamp == NULL || *pTimeStamp == 0 )
|
|
{
|
|
SYSTEMTIME sysTime;
|
|
FILETIME fileTime;
|
|
|
|
// Get current time
|
|
::GetSystemTime( &sysTime );
|
|
|
|
// Convert system time to file time
|
|
::SystemTimeToFileTime( &sysTime, &fileTime );
|
|
|
|
// Compensate for local TZ
|
|
::FileTimeToLocalFileTime( &fileTime, &ftLocal );
|
|
}
|
|
else
|
|
{
|
|
// Compensate for local TZ
|
|
::FileTimeToLocalFileTime( (FILETIME *)pTimeStamp, &ftLocal );
|
|
}
|
|
|
|
// Finally convert it to system time
|
|
::FileTimeToSystemTime( &ftLocal, &stLocal );
|
|
|
|
// Convert timestamp to a date string
|
|
::GetDateFormatW( GetThreadLocale( ),
|
|
DATE_SHORTDATE,
|
|
&stLocal,
|
|
NULL,
|
|
pwszDate,
|
|
sizeof( pwszDate ) / sizeof( pwszDate[0] ));
|
|
|
|
// Convert timestamp to a time string
|
|
::GetTimeFormatW( GetThreadLocale( ),
|
|
0,
|
|
&stLocal,
|
|
NULL,
|
|
pwszTime,
|
|
sizeof( pwszTime ) / sizeof( pwszTime[0] ));
|
|
|
|
// Now combine the strings and return it
|
|
pwszDateTime = (LPWSTR)::CoTaskMemAlloc( ( ::wcslen( pwszDate ) + ::wcslen( pwszTime ) + 2 ) * sizeof( pwszDate[0] ) );
|
|
if ( pwszDateTime == NULL )
|
|
ft.Throw( VSSDBG_VSSADMIN, E_OUTOFMEMORY,
|
|
L"Error from CoTaskMemAlloc, rc: %d",
|
|
::GetLastError() );
|
|
|
|
::wcscpy( pwszDateTime, pwszDate );
|
|
::wcscat( pwszDateTime, L" " );
|
|
::wcscat( pwszDateTime, pwszTime );
|
|
|
|
return pwszDateTime;
|
|
}
|
|
|
|
|
|
LPWSTR LonglongToString(
|
|
IN LONGLONG llValue
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"LonglongToString" );
|
|
|
|
WCHAR wszLL[64];
|
|
LPWSTR pwszRetVal = NULL;
|
|
|
|
::_i64tow( llValue, wszLL, 10 );
|
|
|
|
::VssSafeDuplicateStr( ft, pwszRetVal, wszLL );
|
|
return pwszRetVal;
|
|
}
|
|
|
|
/*++
|
|
|
|
Description:
|
|
|
|
Uses the win32 console functions to get one character of input.
|
|
|
|
--*/
|
|
WCHAR MyGetChar(
|
|
)
|
|
{
|
|
CVssFunctionTracer ft( VSSDBG_VSSADMIN, L"MyGetChar" );
|
|
|
|
DWORD fdwOldMode, fdwMode;
|
|
HANDLE hStdin;
|
|
WCHAR chBuffer[2];
|
|
|
|
hStdin = ::GetStdHandle(STD_INPUT_HANDLE);
|
|
if (hStdin == INVALID_HANDLE_VALUE)
|
|
{
|
|
ft.Throw( VSSDBG_VSSADMIN, HRESULT_FROM_WIN32( ::GetLastError() ),
|
|
L"MyGetChar - Error from GetStdHandle(), rc: %d",
|
|
::GetLastError() );
|
|
}
|
|
|
|
if (!::GetConsoleMode(hStdin, &fdwOldMode))
|
|
{
|
|
ft.Throw( VSSDBG_VSSADMIN, HRESULT_FROM_WIN32( ::GetLastError() ),
|
|
L"MyGetChar - Error from GetConsoleMode(), rc: %d",
|
|
::GetLastError() );
|
|
}
|
|
|
|
fdwMode = fdwOldMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT );
|
|
if (!::SetConsoleMode(hStdin, fdwMode))
|
|
{
|
|
ft.Throw( VSSDBG_VSSADMIN, HRESULT_FROM_WIN32( ::GetLastError() ),
|
|
L"MyGetChar - Error from SetConsoleMode(), rc: %d",
|
|
::GetLastError() );
|
|
}
|
|
|
|
// Flush the console input buffer to make sure there is no queued input
|
|
::FlushConsoleInputBuffer( hStdin );
|
|
|
|
// Without line and echo input modes, ReadFile returns
|
|
// when any input is available.
|
|
DWORD dwBytesRead;
|
|
if (!::ReadConsoleW(hStdin, chBuffer, 1, &dwBytesRead, NULL))
|
|
{
|
|
ft.Throw( VSSDBG_VSSADMIN, HRESULT_FROM_WIN32( ::GetLastError() ),
|
|
L"MyGetChar - Error from ReadConsoleW(), rc: %d",
|
|
::GetLastError() );
|
|
}
|
|
|
|
// Restore the original console mode.
|
|
::SetConsoleMode(hStdin, fdwOldMode);
|
|
|
|
return chBuffer[0];
|
|
}
|
|
|