windows-nt/Source/XPSP1/NT/ds/netapi/svcdlls/lls/server/llsutil.c
2020-09-26 16:20:57 +08:00

1173 lines
29 KiB
C

/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
LlsUtil.c
Abstract:
Author:
Arthur Hanson (arth) Dec 07, 1994
Environment:
Revision History:
Jeff Parham (jeffparh) 12-Jan-1996
o Added WinNtBuildNumberGet() to ascertain the Windows NT build number
running on a given machine.
o Enhanced output of TimeToString().
--*/
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntlsa.h>
#include <windows.h>
#include <stdlib.h>
#include <crypt.h>
#include <wchar.h>
#include <dsgetdc.h>
#include "debug.h"
#include "llssrv.h"
#include "llsevent.h"
//
// NB : Keep this define in sync with client\llsrpc.rc.
//
#define IDS_LICENSEWARNING 1501
const char HeaderString[] = "License Logging System Data File\x01A";
#define HEADER_SIZE 34
typedef struct _LLS_FILE_HEADER {
char Header[HEADER_SIZE];
DWORD Version;
DWORD DataSize;
} LLS_FILE_HEADER, *PLLS_FILE_HEADER;
extern HANDLE gLlsDllHandle;
static HANDLE ghWarningDlgThreadHandle = NULL;
VOID WarningDlgThread( PVOID ThreadParameter );
/////////////////////////////////////////////////////////////////////////
NTSTATUS
EBlock(
PVOID Data,
ULONG DataSize
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
DATA_KEY PublicKey;
CRYPT_BUFFER CryptBuffer;
//
// Init our public key
//
PublicKey.Length = 4;
PublicKey.MaximumLength = 4;
PublicKey.Buffer = LocalAlloc(LPTR, 4);
if (PublicKey.Buffer != NULL) {
((char *) (PublicKey.Buffer))[0] = '7';
((char *) (PublicKey.Buffer))[1] = '7';
((char *) (PublicKey.Buffer))[2] = '7';
((char *) (PublicKey.Buffer))[3] = '7';
CryptBuffer.Length = DataSize;
CryptBuffer.MaximumLength = DataSize;
CryptBuffer.Buffer = (PVOID) Data;
Status = RtlEncryptData2(&CryptBuffer, &PublicKey);
LocalFree(PublicKey.Buffer);
} else
Status = STATUS_NO_MEMORY;
return Status;
} // EBlock
/////////////////////////////////////////////////////////////////////////
NTSTATUS
DeBlock(
PVOID Data,
ULONG DataSize
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
DATA_KEY PublicKey;
CRYPT_BUFFER CryptBuffer;
//
// Init our public key
//
PublicKey.Length = 4;
PublicKey.MaximumLength = 4;
PublicKey.Buffer = LocalAlloc(LPTR, 4);
if (PublicKey.Buffer != NULL) {
((char *) (PublicKey.Buffer))[0] = '7';
((char *) (PublicKey.Buffer))[1] = '7';
((char *) (PublicKey.Buffer))[2] = '7';
((char *) (PublicKey.Buffer))[3] = '7';
CryptBuffer.Length = DataSize;
CryptBuffer.MaximumLength = DataSize;
CryptBuffer.Buffer = (PVOID) Data;
Status = RtlDecryptData2(&CryptBuffer, &PublicKey);
LocalFree(PublicKey.Buffer);
} else
Status = STATUS_NO_MEMORY;
return Status;
} // DeBlock
/////////////////////////////////////////////////////////////////////////
BOOL
FileExists(
LPTSTR FileName
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
return (BOOL) RtlDoesFileExists_U(FileName);
} // FileExists
/////////////////////////////////////////////////////////////////////////
VOID
lsplitpath(
const TCHAR *path,
TCHAR *drive,
TCHAR *dir,
TCHAR *fname,
TCHAR *ext
)
/*++
Routine Description:
Splits a path name into its individual components
Took the _splitpath and _makepath routines and converted them to
be NT (long file name) and Unicode friendly.
Arguments:
Entry:
path - pointer to path name to be parsed
drive - pointer to buffer for drive component, if any
dir - pointer to buffer for subdirectory component, if any
fname - pointer to buffer for file base name component, if any
ext - pointer to buffer for file name extension component, if any
Exit:
drive - pointer to drive string. Includes ':' if a drive was given.
dir - pointer to subdirectory string. Includes leading and
trailing '/' or '\', if any.
fname - pointer to file base name
ext - pointer to file extension, if any. Includes leading '.'.
Return Value:
--*/
{
TCHAR *p;
TCHAR *last_slash = NULL, *dot = NULL;
SIZE_T len;
// init these so we don't exit with bogus values
drive[0] = TEXT('\0');
dir[0] = TEXT('\0');
fname[0] = TEXT('\0');
ext[0] = TEXT('\0');
if (path[0] == TEXT('\0'))
return;
/*+---------------------------------------------------------------------+
| Assume that the path argument has the following form, where any or |
| all of the components may be missing. |
| |
| <drive><dir><fname><ext> |
| |
| drive: |
| 0 to MAX_DRIVE-1 characters, the last of which, if any, is a |
| ':' or a '\' in the case of a UNC path. |
| dir: |
| 0 to _MAX_DIR-1 characters in the form of an absolute path |
| (leading '/' or '\') or relative path, the last of which, if |
| any, must be a '/' or '\'. E.g - |
| |
| absolute path: |
| \top\next\last\ ; or |
| /top/next/last/ |
| relative path: |
| top\next\last\ ; or |
| top/next/last/ |
| Mixed use of '/' and '\' within a path is also tolerated |
| fname: |
| 0 to _MAX_FNAME-1 characters not including the '.' character |
| ext: |
| 0 to _MAX_EXT-1 characters where, if any, the first must be a |
| '.' |
+---------------------------------------------------------------------+*/
// extract drive letter and :, if any
if ( path[0] && (path[1] == TEXT(':')) ) {
if (drive) {
drive[0] = path[0];
drive[1] = path[1];
drive[2] = TEXT('\0');
}
path += 2;
}
// if no drive then check for UNC pathname
if (drive[0] == TEXT('\0'))
if ((path[0] == TEXT('\\')) && (path[1] == TEXT('\\'))) {
// got a UNC path so put server-sharename into drive
drive[0] = path[0];
drive[1] = path[1];
path += 2;
p = &drive[2];
while ((*path != TEXT('\0')) && (*path != TEXT('\\')))
*p++ = *path++;
if (*path == TEXT('\0'))
return;
// now sitting at the share - copy this as well (copy slash first)
*p++ = *path++;
while ((*path != TEXT('\0')) && (*path != TEXT('\\')))
*p++ = *path++;
// tack on terminating NULL
*p = TEXT('\0');
}
/*+---------------------------------------------------------------------+
| extract path string, if any. Path now points to the first character|
| of the path, if any, or the filename or extension, if no path was |
| specified. Scan ahead for the last occurence, if any, of a '/' or |
| '\' path separator character. If none is found, there is no path. |
| We will also note the last '.' character found, if any, to aid in |
| handling the extension. |
+---------------------------------------------------------------------+*/
for (last_slash = NULL, p = (TCHAR *)path; *p; p++) {
if (*p == TEXT('/') || *p == TEXT('\\'))
// point to one beyond for later copy
last_slash = p + 1;
else if (*p == TEXT('.'))
dot = p;
}
if (last_slash) {
// found a path - copy up through last_slash or max. characters allowed,
// whichever is smaller
if (dir) {
len = __min((last_slash - path), (_MAX_DIR - 1));
lstrcpyn(dir, path, (int)len + 1);
dir[len] = TEXT('\0');
}
path = last_slash;
}
/*+---------------------------------------------------------------------+
| extract file name and extension, if any. Path now points to the |
| first character of the file name, if any, or the extension if no |
| file name was given. Dot points to the '.' beginning the extension,|
| if any. |
+---------------------------------------------------------------------+*/
if (dot && (dot >= path)) {
// found the marker for an extension - copy the file name up to the
// '.'.
if (fname) {
len = __min((dot - path), (_MAX_FNAME - 1));
lstrcpyn(fname, path, (int)len + 1);
*(fname + len) = TEXT('\0');
}
// now we can get the extension - remember that p still points to the
// terminating nul character of path.
if (ext) {
len = __min((p - dot), (_MAX_EXT - 1));
lstrcpyn(ext, dot, (int)len + 1);
ext[len] = TEXT('\0');
}
}
else {
// found no extension, give empty extension and copy rest of string
// into fname.
if (fname) {
len = __min((p - path), (_MAX_FNAME - 1));
lstrcpyn(fname, path, (int)len + 1);
fname[len] = TEXT('\0');
}
if (ext) {
*ext = TEXT('\0');
}
}
} // lsplitpath
/////////////////////////////////////////////////////////////////////////
VOID
lmakepath(
TCHAR *path,
const TCHAR *drive,
const TCHAR *dir,
const TCHAR *fname,
const TCHAR *ext
)
/*++
Routine Description:
Create a path name from its individual components.
Arguments:
Entry:
char *path - pointer to buffer for constructed path
char *drive - pointer to drive component, may or may not contain
trailing ':'
char *dir - pointer to subdirectory component, may or may not include
leading and/or trailing '/' or '\' characters
char *fname - pointer to file base name component
char *ext - pointer to extension component, may or may not contain
a leading '.'.
Exit:
path - pointer to constructed path name
Return Value:
--*/
{
const TCHAR *p;
/*+---------------------------------------------------------------------+
| we assume that the arguments are in the following form (although we |
| do not diagnose invalid arguments or illegal filenames (such as |
| names longer than 8.3 or with illegal characters in them) |
| |
| drive: |
| A or A: |
| dir: |
| \top\next\last\ ; or |
| /top/next/last/ ; or |
| |
| either of the above forms with either/both the leading and |
| trailing / or \ removed. Mixed use of '/' and '\' is also |
| tolerated |
| fname: |
| any valid file name |
| ext: |
| any valid extension (none if empty or null ) |
+---------------------------------------------------------------------+*/
// copy drive
if (drive && *drive)
while (*drive)
*path++ = *drive++;
// copy dir
if ((p = dir) && *p) {
do {
*path++ = *p++;
}
while (*p);
if ((*(p-1) != TEXT('/')) && (*(p-1) != TEXT('\\'))) {
*path++ = TEXT('\\');
}
}
// copy fname
if (p = fname) {
while (*p) {
*path++ = *p++;
}
}
// copy ext, including 0-terminator - check to see if a '.' needs to be
// inserted.
if (p = ext) {
if (*p && *p != TEXT('.')) {
*path++ = TEXT('.');
}
while (*path++ = *p++)
;
}
else {
// better add the 0-terminator
*path = TEXT('\0');
}
} // lmakepath
/////////////////////////////////////////////////////////////////////////
VOID
FileBackupCreate(
LPTSTR Path
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
HANDLE hFile = NULL;
DWORD dwFileNumber = 0;
TCHAR Drive[_MAX_DRIVE + 1];
TCHAR Dir[_MAX_DIR + 1];
TCHAR FileName[_MAX_FNAME + 1];
TCHAR Ext[_MAX_EXT + 1];
TCHAR NewExt[_MAX_EXT + 1];
TCHAR NewPath[MAX_PATH + 1];
//
// Make sure file exists
//
if (!FileExists(FileName))
return;
//
// Split name into constituent parts...
//
lsplitpath(Path, Drive, Dir, FileName, Ext);
// Find next backup number...
// Files are backed up as .xxx where xxx is a number in the form .001,
// the first backup is stored as .001, second as .002, etc...
do {
//
// Create new file name with backup extension
//
dwFileNumber++;
wsprintf(NewExt, TEXT("%03u"), dwFileNumber);
lmakepath(NewPath, Drive, Dir, FileName, NewExt);
} while ( FileExists(NewPath) );
MoveFile( Path, NewPath );
} // FileBackupCreate
/////////////////////////////////////////////////////////////////////////
HANDLE
LlsFileInit(
LPTSTR FileName,
DWORD Version,
DWORD DataSize
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
HANDLE hFile = NULL;
LLS_FILE_HEADER Header;
DWORD BytesWritten;
#ifdef DEBUG
if (TraceFlags & (TRACE_FUNCTION_TRACE | TRACE_DATABASE))
dprintf(TEXT("LLS TRACE: LlsFileInit\n"));
#endif
if (FileName == NULL)
return NULL;
strcpy(Header.Header, HeaderString);
Header.Version = Version;
Header.DataSize = DataSize;
SetFileAttributes(FileName, FILE_ATTRIBUTE_NORMAL);
hFile = CreateFile(FileName, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
if (!WriteFile(hFile, &Header, sizeof(LLS_FILE_HEADER), &BytesWritten, NULL)) {
CloseHandle(hFile);
hFile = NULL;
}
} else {
return NULL;
}
return hFile;
} // LlsFileInit
/////////////////////////////////////////////////////////////////////////
HANDLE
LlsFileCheck(
LPTSTR FileName,
LPDWORD Version,
LPDWORD DataSize
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
BOOL FileOK = FALSE;
HANDLE hFile = NULL;
LLS_FILE_HEADER Header;
DWORD FileSize;
DWORD BytesRead;
#ifdef DEBUG
if (TraceFlags & (TRACE_FUNCTION_TRACE | TRACE_DATABASE))
dprintf(TEXT("LLS TRACE: LlsFileCheck\n"));
#endif
if (FileName == NULL)
return NULL;
//
// We are assuming the file exists
//
SetFileAttributes(FileName, FILE_ATTRIBUTE_NORMAL);
hFile = CreateFile(FileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
FileSize = GetFileSize(hFile, NULL);
//
// Make sure there is enough data there to read
//
if (FileSize > (sizeof(LLS_FILE_HEADER) + 1)) {
if (ReadFile(hFile, &Header, sizeof(LLS_FILE_HEADER), &BytesRead, NULL)) {
if ( !_strcmpi(Header.Header, HeaderString) ) {
//
// Data checks out - so return datalength
//
*Version = Header.Version;
*DataSize = Header.DataSize;
FileOK = TRUE;
}
}
}
//
// If we opened the file and something was wrong - close it.
//
if (!FileOK) {
CloseHandle(hFile);
hFile = NULL;
}
} else {
return NULL;
}
return hFile;
} // LlsFileCheck
/////////////////////////////////////////////////////////////////////////
DWORD
DateSystemGet(
)
/*++
Routine Description:
Arguments:
Return Value:
Seconds since midnight.
--*/
{
DWORD Seconds = 0;
LARGE_INTEGER SysTime;
NtQuerySystemTime(&SysTime);
RtlTimeToSecondsSince1980(&SysTime, &Seconds);
return Seconds;
} // DateSystemGet
/////////////////////////////////////////////////////////////////////////
DWORD
DateLocalGet(
)
/*++
Routine Description:
Arguments:
Return Value:
Seconds since midnight.
--*/
{
DWORD Seconds = 0;
LARGE_INTEGER SysTime, LocalTime;
NtQuerySystemTime(&SysTime);
RtlSystemTimeToLocalTime(&SysTime, &LocalTime);
RtlTimeToSecondsSince1980(&LocalTime, &Seconds);
return Seconds;
} // DateLocalGet
/////////////////////////////////////////////////////////////////////////
DWORD
InAWorkgroup(
VOID
)
/*++
Routine Description:
This function determines whether we are a member of a domain, or of
a workgroup. First it checks to make sure we're running on a Windows NT
system (otherwise we're obviously in a domain) and if so, queries LSA
to get the Primary domain SID, if this is NULL, we're in a workgroup.
If we fail for some random unexpected reason, we'll pretend we're in a
workgroup (it's more restrictive).
Arguments:
None
Return Value:
TRUE - We're in a workgroup
FALSE - We're in a domain
--*/
{
NT_PRODUCT_TYPE ProductType;
OBJECT_ATTRIBUTES ObjectAttributes;
LSA_HANDLE Handle;
NTSTATUS Status;
PPOLICY_PRIMARY_DOMAIN_INFO PolicyPrimaryDomainInfo = NULL;
DWORD Result = FALSE;
Status = RtlGetNtProductType(&ProductType);
if (!NT_SUCCESS(Status)) {
#if DBG
dprintf(TEXT("ERROR LLS Could not get Product type\n"));
#endif
return TRUE;
}
if (ProductType == NtProductLanManNt) {
return(FALSE);
}
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, 0, NULL);
Status = LsaOpenPolicy(NULL,
&ObjectAttributes,
POLICY_VIEW_LOCAL_INFORMATION,
&Handle);
if (!NT_SUCCESS(Status)) {
#if DBG
dprintf(TEXT("ERROR LLS: Could not open LSA Policy Database\n"));
#endif
return TRUE;
}
Status = LsaQueryInformationPolicy(Handle, PolicyPrimaryDomainInformation,
(PVOID *) &PolicyPrimaryDomainInfo);
if (NT_SUCCESS(Status)) {
if (PolicyPrimaryDomainInfo->Sid == NULL) {
Result = TRUE;
}
else {
Result = FALSE;
}
}
if (PolicyPrimaryDomainInfo) {
LsaFreeMemory((PVOID)PolicyPrimaryDomainInfo);
}
LsaClose(Handle);
return(Result);
} // InAWorkgroup
/////////////////////////////////////////////////////////////////////////
VOID
LogEvent(
DWORD MessageId,
DWORD NumberOfSubStrings,
LPWSTR *SubStrings,
DWORD ErrorCode
)
{
HANDLE LogHandle;
WORD wEventType;
LogHandle = RegisterEventSourceW (
NULL,
TEXT("LicenseService")
);
if (LogHandle == NULL) {
#if DBG
dprintf(TEXT("LLS RegisterEventSourceW failed %lu\n"), GetLastError());
#endif
return;
}
switch ( MessageId >> 30 )
{
case STATUS_SEVERITY_INFORMATIONAL:
case STATUS_SEVERITY_SUCCESS:
wEventType = EVENTLOG_INFORMATION_TYPE;
break;
case STATUS_SEVERITY_WARNING:
wEventType = EVENTLOG_WARNING_TYPE;
break;
case STATUS_SEVERITY_ERROR:
default:
wEventType = EVENTLOG_ERROR_TYPE;
break;
}
if (ErrorCode == ERROR_SUCCESS) {
//
// No error codes were specified
//
(void) ReportEventW(
LogHandle,
wEventType,
0, // event category
MessageId,
NULL,
(WORD)NumberOfSubStrings,
0,
SubStrings,
(PVOID) NULL
);
}
else {
//
// Log the error code specified
//
(void) ReportEventW(
LogHandle,
wEventType,
0, // event category
MessageId,
NULL,
(WORD)NumberOfSubStrings,
sizeof(DWORD),
SubStrings,
(PVOID) &ErrorCode
);
}
DeregisterEventSource(LogHandle);
} // LogEvent
#define THROTTLE_WRAPAROUND 24
//
// Reduce the frequency of logging
// No need for the limit to be exact
//
/////////////////////////////////////////////////////////////////////////
VOID
ThrottleLogEvent(
DWORD MessageId,
DWORD NumberOfSubStrings,
LPWSTR *SubStrings,
DWORD ErrorCode
)
{
static LONG lLogged = THROTTLE_WRAPAROUND;
LONG lResult;
lResult = InterlockedIncrement(&lLogged);
if (THROTTLE_WRAPAROUND <= lResult)
{
LogEvent(
MessageId,
NumberOfSubStrings,
SubStrings,
ErrorCode );
lLogged = 0;
}
}
/////////////////////////////////////////////////////////////////////////
VOID
LicenseCapacityWarningDlg(DWORD dwCapacityState)
{
//
// NB : The ServiceLock critical section is entered for the duration
// of this routine. No serialization issues.
//
if (ghWarningDlgThreadHandle == NULL) {
//
// No action necessary if this fails.
//
DWORD Ignore;
DWORD * pWarningMessageID;
pWarningMessageID = LocalAlloc(LPTR, sizeof(DWORD));
if ( pWarningMessageID != NULL ) {
switch( dwCapacityState ) {
case LICENSE_CAPACITY_NEAR_MAXIMUM:
*pWarningMessageID = LLS_EVENT_NOTIFY_LICENSES_NEAR_MAX;
break;
case LICENSE_CAPACITY_AT_MAXIMUM:
*pWarningMessageID = LLS_EVENT_NOTIFY_LICENSES_AT_MAX;
break;
case LICENSE_CAPACITY_EXCEEDED:
*pWarningMessageID = LLS_EVENT_NOTIFY_LICENSES_EXCEEDED;
break;
default:
*pWarningMessageID = LLS_EVENT_NOTIFY_LICENSES_EXCEEDED;
};
ghWarningDlgThreadHandle = CreateThread(NULL,
0L,
(LPTHREAD_START_ROUTINE)
WarningDlgThread,
pWarningMessageID,
0L,
&Ignore);
if (ghWarningDlgThreadHandle == NULL)
{
//
// CreateThread failed
//
LocalFree(pWarningMessageID);
}
}
}
}
/////////////////////////////////////////////////////////////////////////
VOID
WarningDlgThread(
PVOID ThreadParameter)
{
LPTSTR pszWarningMessage = NULL;
DWORD * pWarningMessageID;
HANDLE hThread;
TCHAR szWarningTitle[256] = TEXT("");
ASSERT(ThreadParameter != NULL);
pWarningMessageID = (DWORD *)ThreadParameter;
//
// NB : The .dll should already have been loaded in MasterServiceListInit
// on service startup. This logic exists here for the case where
// the code invoked on initialization should fail.
//
// It is OK if another thread should simulataneously initialize
// gLlsDllHandle. Worst case, there will be an orphaned handle
// to the .dll. But the .dll is loaded for the lifetime of this
// .exe, so no big deal.
//
if ( gLlsDllHandle == NULL ) {
gLlsDllHandle = LoadLibrary(TEXT("LLSRPC.DLL"));
}
if ( gLlsDllHandle != NULL) {
DWORD ccWarningMessage;
//
// Fetch the dialog title.
//
LoadString(gLlsDllHandle,
IDS_LICENSEWARNING,
szWarningTitle,
sizeof(szWarningTitle)/sizeof(TCHAR));
//
// Fetch the dialog message.
//
ccWarningMessage = FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_HMODULE |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
gLlsDllHandle,
*pWarningMessageID,
0,
(LPTSTR)&pszWarningMessage,
0,
NULL);
if ( ccWarningMessage > 2 ) {
//
// Strip the trailing <CR><LF> format message always adds.
//
pszWarningMessage[ccWarningMessage - 2] = TEXT('\0');
MessageBox(NULL,
pszWarningMessage,
szWarningTitle,
MB_ICONWARNING | MB_OK | MB_SYSTEMMODAL |
MB_SERVICE_NOTIFICATION);
}
if ( pszWarningMessage != NULL) {
LocalFree(pszWarningMessage);
}
}
LocalFree(pWarningMessageID);
//
// By closing the handle, we allow the system to remove all remaining
// traces of this thread.
//
hThread = ghWarningDlgThreadHandle;
ghWarningDlgThreadHandle = NULL;
CloseHandle(hThread);
}
/////////////////////////////////////////////////////////////////////////
DWORD WinNtBuildNumberGet( LPTSTR pszServerName, LPDWORD pdwBuildNumber )
/*++
Routine Description:
Retrieve the build number of Windows NT running on a given machine.
Arguments:
pszServerName (LPTSTR)
Name of the server to check.
pdwBuildNumber (LPDWORD)
On return, holds the build number of the server (e.g., 1057 for the
release version of Windows NT 3.51).
Return Value:
ERROR_SUCCESS or Win error code.
--*/
{
LONG lError;
HKEY hKeyLocalMachine;
lError = RegConnectRegistry( pszServerName, HKEY_LOCAL_MACHINE, &hKeyLocalMachine );
if ( ERROR_SUCCESS != lError )
{
#if DBG
dprintf( TEXT("WinNtBuildNumberGet(): Could not connect to remote registry, error %ld.\n"), lError );
#endif
}
else
{
HKEY hKeyCurrentVersion;
lError = RegOpenKeyEx( hKeyLocalMachine,
TEXT( "Software\\Microsoft\\Windows NT\\CurrentVersion" ),
0,
KEY_READ,
&hKeyCurrentVersion );
if ( ERROR_SUCCESS != lError )
{
#if DBG
dprintf( TEXT("WinNtBuildNumberGet(): Could not open key, error %ld.\n"), lError );
#endif
}
else
{
DWORD dwType;
TCHAR szWinNtBuildNumber[ 16 ];
DWORD cbWinNtBuildNumber = sizeof( szWinNtBuildNumber );
lError = RegQueryValueEx( hKeyCurrentVersion,
TEXT( "CurrentBuildNumber" ),
NULL,
&dwType,
(LPBYTE) &szWinNtBuildNumber,
&cbWinNtBuildNumber );
if ( ERROR_SUCCESS != lError )
{
#if DBG
dprintf( TEXT("WinNtBuildNumberGet(): Could not query value, error %ld.\n"), lError );
#endif
}
else
{
*pdwBuildNumber = (DWORD) _wtol( szWinNtBuildNumber );
}
RegCloseKey( hKeyCurrentVersion );
}
RegCloseKey( hKeyLocalMachine );
}
return (DWORD) lError;
}
#if DBG
/////////////////////////////////////////////////////////////////////////
LPTSTR
TimeToString(
ULONG Seconds
)
{
TIME_FIELDS tf;
LARGE_INTEGER Time, LTime;
static TCHAR TimeString[100];
if ( 0 == Seconds )
{
lstrcpy(TimeString, TEXT("None"));
}
else
{
RtlSecondsSince1980ToTime(Seconds, &Time);
RtlSystemTimeToLocalTime(&Time, &LTime);
RtlTimeToTimeFields(&LTime, &tf);
wsprintf(TimeString, TEXT("%02hd/%02hd/%04hd @ %02hd:%02hd:%02hd"), tf.Month, tf.Day, tf.Year, tf.Hour, tf.Minute, tf.Second);
}
return TimeString;
} // TimeToString
#endif