MSDN 에서 보면 GetVersionEx() 함수를 이용하여, 윈도우의 각종 버전 및 플래폼
그리고, 서비스팩 정보를 이용할 수 있는 샘플 자료가 있습니다.
위 자료를 좀 개선해서, Windows 2008 Servers 및 Windows Vista 에서도 정상적으로 동작하는
모델을 만들어 봤습니다. 클래스의 이름은 CWindowsVersion 입니다.
또한, 기존에 32 비트시스템에서 제작되어진 어플리케이션이 64 비트 윈도우에서 구동될 때
호환모드로 구동하게 되는데, 별다른 문제는 없지만 레지스트리를 액세스할 때 다음과 같은
문제가 발생합니다.
32비트 호환모드로 구동되는 프로그램은 HKEY_LOCAL_MACHINE\SOFTWARE 경로를
직접 액세스 할 수 없습니다.
64비트 전용으로 생성된 소프트웨어는 문제가 없지만 32비트로 만들어져 호환모드로 동작할 경우는 HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node 아래처럼 32비트 응용프로그램을 위한
전용 경로를 사용해야합니다.
이것을 일일이 하나씩 변경하기 힘들어서, 64비트 운영체제에서 테스트해야하는 프로그램중에
32트로 만들어져 있고, 위의 경로및 그 경로의 하위를 액세스하는 경우 자동으로 경로를 변경해주어
동작하는데 문제가 없도록 API를 만들어 봤습니다.
소스는 Visual C++ 6.0, SP6, Enterprize Edition, 영문버전에서 작성되었으며
테스트는 Windows XP 32/64, Windows Server 2003 32/64, Windows Vista 32/64, Windows Server 2008 64
에서 테스트를 마쳤습니다.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 작성 : 2008-09-11 CROWBACK
// 수정 : 2008-09-17 CROWBACK
// 기존에 클래스 형태에서 WOW 라는 네임스페이스로 감싼 API 형태로 변경함.
// 특별히 멤버를 가진것도 아닌데, 객체를 선언하고 사용하는 불편을 제거하기 위함.
//
// 32비트로 컴파일된 실행파일이 64비트 시스템의 HKEY_LOCAL_MACHINE 의 SOFTWARE
// 영역 액세스의 접근이 차단되는 것을 하위 Wow6432Node로 자동 매칭 시킨다.
// 이러한 작업을 위한 Win32 API를 래핑한 함수들.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define MAX_REGVALUE 8196
typedef struct
{
_bstr_t name;
BYTE* data;
DWORD length;
DWORD type;
} TEnumValue;
//////////////////////////////////////////////////////////////////////////////
// 아래 항목들은 Win32 API 양식을 그대로 따른다. 기존 API들과 문법상 차이를 두지
// 않음으로써, 기존 소스 포팅에 도움이 되도록 한다.
LONG RegOpenKey(__in HKEY hKey, __in_opt LPCSTR lpSubKey, __out PHKEY phkResult);
LONG RegOpenKeyEx(__in HKEY hKey, __in LPCTSTR lpSubKey, DWORD ulOptions, __in REGSAM samDesired, __out PHKEY phkResult);
LONG RegCloseKey(__in HKEY hKey);
//////////////////////////////////////////////////////////////////////////////
// 아래 항목들은 작업의 편의를 위해서 새로 추가한 항목들이다.
// 각각의 기능은 별도로 석으로 설명한다.
// 열기에 실패하면 키를 강제로 생성한다. 그래로 실패하면 에러리턴.
LONG RegOpenKeyForce(__in HKEY hKey, __in_opt LPCSTR lpSubKey, __out PHKEY phkResult);
// 서브 아이템 여부와 상관없이, 키 전체를 강제로 삭제함.
LONG RegDeleteKeyForce(__in HKEY hKey, __in LPCTSTR lpSubKey);
// 서브 아이템 여부와 상관없이, 키 이름만 남기고 안에꺼를 몽땅 지움. (서브키 및 밸류 모두..)
LONG RegDeleteKeyEmpty(__in HKEY hKey, __in LPCTSTR lpSubKey);
// 특정 레지 경로를 지정한 후, 값을 직접 지정할 수 있다.
LONG RegSetValueDirect(__in HKEY hKey, __in LPCTSTR lpSubKey, __in LPCTSTR lpValueName, __in DWORD dwType, __in const BYTE* lpData, __in DWORD cbData);
// 특정 레지 경로의 값들을 vector에 한꺼번에 몽땅 읽어와 목록을 작성한다.
LONG RegEnumValueList(__in HKEY hKey, __in LPCTSTR lpSubKey, std::vector<TEnumValue>& array);
// RegEnumValueList 함수와 매칭되는 것으로, 읽어온 것들은 내부적으로 메모리를 할당하므로
// 모두 사용한 후에는 아래 함수를 이용하여 해제하여준다.
VOID RegEnumValueClear(std::vector<TEnumValue>& array);
// 특정 레지 경로의 하위 키 값들을 vector 에 한꺼번에 읽어와 목록을 작성한다.
LONG RegEnumKeyList(__in HKEY hKey, __in LPCTSTR lpSubKey, std::vector<_bstr_t>& array);
// 지정된 키를 다른 곳으로 복사한다. (하위 키 및 모든 값 포함..)
LONG RegCopyKey(HKEY hkeySrc, LPCTSTR szSrcSubKey, HKEY hkeyDest);
// 지정된 키를 다른 곳으로 이동한다. (하위 키 및 모든 값 포함..)
LONG RegMoveKey(HKEY hkeySrc, LPCTSTR szSrcSubKey, HKEY hkeyDest);
// 지정된 키의 이름을 변경한다.
LONG RegRenameKey(HKEY hkeySrc, const char* szSrcSUB, HKEY hkeyDest, const char* szDstSUB);
// 지정된 키 값을 reg 파일로 익스포트 시킨다.
BOOL RegExport(LPCTSTR szSrcKey, LPCTSTR szFilePath);
};
C나 C++에서 수식을 연산하다 잘못된 메모리를 읽거나, 저장된 파일을 읽어들이거나 0으로 나누거나 등등의 이유로 실수값에 -1.#INF0 과 같은 값이 지정된 것을 본적이 있을것이다. 혹은 -1.#IND0 과 같은 것도 있다.
.#IND Not a Number <= 숫자가 아닌값(실수체계에서 나올 수 없는 값과 같은 경우) .#INF Infinite Number <= 0으로 나누는 등으로 인하여 무한히 큰값
숫자가 아닌값을 검출할 때는 CRT의 _isnan() 무한대의 숫자를 검출할 때는 CRT의 _finite() 주어진 숫자가 의심스러울 때 혹은 상태를 검출할 때는 _fpclass() 모두 float.h 에 선언되어 있습니다.
제목에서 처럼 0으로 나누어졌을 때는 숫자가 무한히 커져 .#INF00 의 무한값을 나타내고 다른 예로 double a = sqrt(-1) (데브피아 Neo.L 님께서 제시해주신 예시) 과 같은 경우는 실수체계에서 표현할 수 없는 숫자라는 의미로 .#IND00 과 같이 표기됩니다.
이 부분은 IEEE754,854 문서에 표기된 표준 표기에 의한것 (데브피아 stiletto 님께서 표기해줌) 으로 모두 정상적인 표기 방법입니다.
다행한것이 설계를 그렇게 한것인지, 저런 값이 나온다고 하여 연산하다가 죽는 경우는 없다는 점이다. 단, 필요한 요소에서 꼭 확인하여 엉뚱한 결과로 인한 피해를 줄이자.
잘 모르는 내용을 올리다가 망신살도 좀 뻗치고.. 아무튼 이런 기회에 새로운걸 공부해 보네요.
입력없는 콘솔 프로그램 같은 경우는 그냥 서비스에서 바로 수행하면 되지만 입력을 받는 콘솔이나, GUI를 가진 프로그램을 띄워야할 경우는 프로세스 목록에만 나오고.. 깜깜 무소식이다.
일반 응용프로그램에서야 WTSRegisterSessionNotification 함수를 이용하여 아주 쉽게 로그온된 시점을 구할 수 있지만 불행하게도 서비스에서는 사용할 수 없다.
또한 서비스 시작시에 응용 프로그램을 수행시키면, 아직 로그온 되기 전이기때문에 저런 설정을 다 해놓는다 하여도 로그온된 화면에는 아무것도 보이지 않을 수 있다.
1. 꼭 서비스로 관리해야하는가? 2. 입력을 가지는 콘솔이나 GUI를 가졌는가? 3. 서비스 구동시에 시작해야 하는가? 4. 로그온 된 데스크탑에 보여주어야 하는가?
위 사항들을 잘 판단하고 그래도 꼭 필요하다면 다음과 같은 정보를 구해야한다. 1. 서비스로 개발하기 2. 데스크탑과 연동하는 옵션 3. 로그온된 시점 구하기
1. 서비스로 개발하기 위 사항은 이미 자료가 많이 나와있고, 대부분 알려진 자료이므로 그냥 좋은 자료를 찾는다 ^^;
2. 데스크탑과 연동하는 옵션 첫번째로 가장 쉬운 방법은 서비스 관리자를 이용하는 방법이다.
그림에서 보이는것 처럼 로그온 설정시에 서비스와 데스크탑 상호 작용 허용에 첵크를 해줌으로써 아주 간단하게 해결할 수 있다.
두번째는 서비스를 인스톨할 때, 즉 CreateService 함수를 이용하여 서비스를 설치할 때 다섯번째 인자에 SERVICE_INTERACTIVE_PROCESS 를 함께 넣어주는 것이다. 이것은 첫번째 방법을 프로그램적으로 해결하는 것이다.
3. 로그온된 시점 구하기 로그온된 시점을 구하는 방법도 그리 어렵지 않다. 하지만 제약사항이 많아서.. 특히나 지금 처리하는 방법은 Windows XP 이상, Windows Server 2003 이상에서만 동작하는 방법이므로, 하위 버전 윈도우를 처리하는 경우에는 해당사항이 없다. -_-;;; (예전에는 이벤트 로그를 뒤적거려서 처리했었다...)
서비스를 핸들하기 위하여 RegisterServiceCtrlHandler 함수 대신에 좀더 구체적인 정보를 전달해주는 RegisterServiceCtrlHandlerEx 를 이용하는 방법이다.
RegisterServiceCtrlHandlerEx API는 시스템의 세션정보 변화를 감지하는 이벤트인 WM_WTSSESSION_CHANGE 를 처리할 수 있도록 해준다.
SetServiceStatus 에서 SERVICE_STATUS 값의 dwControlsAccepted 값에 꼭 SERVICE_ACCEPT_SESSIONCHANGE 를 함께 넣어주도록 하자.
이렇게 RegisterServiceCtrlHandlerEx 에 전달된 콜백함수에는 다음과 같은 인자가 있다. DWORD WINAPI HandlerEx( [in] DWORD dwControl, [in] DWORD dwEventType, [in] LPVOID lpEventData, [in] LPVOID lpContext );
저기서 첫번째 인자에 SERVICE_CONTROL_SESSIONCHANGE 가 넘어오면 로그온 세션에 무언가 변화가 생긴것이다.
두번째 인자로 어떤 변화가 발생하였는지를 감지할 수 있다. MSDN의 설명을 참고해보면...
Value
Meaning
WTS_CONSOLE_CONNECT 0x1
A session was connected to the console terminal.
WTS_CONSOLE_DISCONNECT 0x2
A session was disconnected from the console terminal.
WTS_REMOTE_CONNECT 0x3
A session was connected to the remote terminal.
WTS_REMOTE_DISCONNECT 0x4
A session was disconnected from the remote terminal.
WTS_SESSION_LOGON 0x5
A user has logged on to the session.
WTS_SESSION_LOGOFF 0x6
A user has logged off the session.
WTS_SESSION_LOCK 0x7
A session has been locked.
WTS_SESSION_UNLOCK 0x8
A session has been unlocked.
WTS_SESSION_REMOTE_CONTROL 0x9
A session has changed its remote controlled status. To determine the status, call GetSystemMetrics and check the SM_REMOTECONTROL metric.
그러므로 위시점에서 WTS_SESSION_LOGON 일 경우에 프로그램을 기동시키면 정확하게 로그온된 시점에 프로그램이 구동하게 된다.
위와 같은 자료가 필요한 이유는, 배포된 프로그램에 시간을 이용해서 확인하는 작업등이 존재할경우 CMOS나, OS시간을 변경시켜버리면 확인하기 힘든 부분이 존재하기 때문이다. 여러가지 방식으로 막을 수는 있겠지만, 인터넷에 연결되어져야 하는 컴퓨터라면 혹은 인터넷이 되어야만 사용할 수 있는 프로그램이라면 위 방법으로 쉽게 해결할 수 있다.
This article was previously published under Q167296
SUMMARY
Under UNIX platforms, file times are maintained in the form of a ANSI C runtime arithmetic type named 'time_t', which represents seconds since midnight January 1, 1970 UTC (coordinated universal time).
Under Win32 platforms, file times are maintained primarily in the form of a 64-bit FILETIME structure, which represents the number of 100-nanosecond intervals since January 1, 1601 UTC (coordinate universal time).
This article shows how to convert UNIX time to other Win32 time formats.
MORE INFORMATION
The following function converts a filetime in the UNIX time_t format to a Win32 FILETIME format. Note that time_t is a 32-bit value and FILETIME is a 64-bit structure, so the Win32 function, Int32x32To64() is used in the following function:
#include <winbase.h>
#include <winnt.h>
#include <time.h>
void UnixTimeToFileTime(time_t t, LPFILETIME pft)
{
// Note that LONGLONG is a 64-bit value
LONGLONG ll;
ll = Int32x32To64(t, 10000000) + 116444736000000000;
pft->dwLowDateTime = (DWORD)ll;
pft->dwHighDateTime = ll >> 32;
}
Once the UNIX time is converted to a FILETIME structure, other Win32 time formats can be easily obtained by using Win32 functions such as FileTimeToSystemTime() and FileTimeToDosDateTime().
권한에 관한 부분은 상당히 방대한 양이라.. 그냥 필요해서 쓰는것만 추려봤습니다. MSDN에 잘 나와았지만 대부분 WCHAR 타입의 인자를 넘겨야해서 그냥 껍데기를 씌운 형태입니다. ^^;
1. 유저 영역 -> 유저 생성, 유저 삭제, 유저 목록, 암호 변경, 유저가 속한 그룹 목록, 유저 정보등..
2. 그룹 영역 -> 그룹 생성, 그룹 삭제, 그룹 목록, 그룹에 유저 추가, 그룹에서 유저 삭제등..
3. 권한 영역 -> 유저의 SID 얻어오기, SID를 문자열로 만들기, 현재 유저의 권한 얻어오기 권한의 활성화/비활성화, 프로세스에 속한 권한 목록 얻어오기, 권한 상승 프로세스 소유자 얻어오기 등등...
// Policy.h: interface for the CPolicy class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_POLICY_H__8DE86AF1_C525_4546_A661_0FC4D12902EF__INCLUDED_)
#define AFX_POLICY_H__8DE86AF1_C525_4546_A661_0FC4D12902EF__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define RTN_OK 0
#define RTN_USAGE 1
#define RTN_ERROR 13
#include <ntsecapi.h>
#include <vector>
#include <comutil.h>
#include <lm.h>
#include <sddl.h>
#include <Ntsecapi.h>
namespace CLM // Control Local Machine
{
LPCTSTR GetLogonUser();
LPCTSTR GetHostName();
LPCTSTR GetDomainName();
LPWCH MBtoWC(LPCTSTR src, PWCHAR dst, int dstlen);
LPTSTR WCtoMB(LPCWCH src, LPTSTR dst, int dstlen);
void InitLsaString(PLSA_UNICODE_STRING LsaString, LPWSTR String);
NTSTATUS OpenPolicy(
LPCTSTR ServerName, // machine to open policy
DWORD DesiredAccess, // desired access to policy
PLSA_HANDLE PolicyHandle); // resultant policy handle, must be LsaClose(PolicyHandle)
NTSTATUS OpenPolicy(
PLSA_HANDLE PolicyHandle); // resultant policy handle, must be LsaClose(PolicyHandle)
BOOL GetUserSid(
LPCTSTR servername, // where to lookup account
LPCTSTR username, // account of interest
PSID *Sid); // resultant buffer containing SID, must be HeapFree(GetProcessHeap(), 0, pSid);
BOOL GetCurrentUserSid(
PSID *pSid); // resultant buffer containing SID, must be HeapFree(GetProcessHeap(), 0, pSid);
BOOL GetSIDToString(PSID sid, LPTSTR& strstring);
namespace USER
{
/* Declared in Lmaccess.h; include Lm.h. Use Netapi32.lib.
NetUserAdd Adds a user account and assigns a password and privilege level.
NetUserChangePassword Changes a user's password for a specified network server or domain.
NetUserDel Deletes a user account from the server.
NetUserEnum Lists all user accounts on a server.
NetUserGetGroups Returns a list of global group names to which a user belongs.
NetUserGetInfo Returns information about a particular user account on a server.
NetUserGetLocalGroups Returns a list of local group names to which a user belongs.
NetUserSetGroups Sets global group memberships for a specified user account.
NetUserSetInfo Sets the password and other elements of a user account.
*/
NET_API_STATUS NetUserAdd(
LPCTSTR servername, // NULL is localcomputer
LPCTSTR username, // Not NULL
LPCTSTR password, // allow NULL
LPCTSTR comment, // allow NULL
DWORD privilege, // USER_PRIV_GUEST, USER_PRIV_USER, USER_PRIV_ADMIN
DWORD flags); // 권한에 대한 제한. UF_SCRIPT | UF_DONT_EXPIRE_PASSWD 기타 등등..
NET_API_STATUS NetUserChangePassword(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR username, // NULL is current logon user
LPCTSTR oldpassword, // current password, Not NULL
LPCTSTR newpassword); // new password, Not NULL
NET_API_STATUS NetUserDel(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR username); // Not NULL
NET_API_STATUS NetUserEnum(
LPCTSTR domainname, // NULL is localcomputer
LPDWORD dwCount, // user count
LPUSER_INFO_20& userinfos); // user info array, must be free -> NetApiBufferFree(userinfos);
NET_API_STATUS NetUserGetGroups(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR username, // NULL is current user
std::vector<_bstr_t>& parray); // global group name array
NET_API_STATUS NetUserGetInfo(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR username, // NULL is current user
LPUSER_INFO_20& userinfos); // user info array, must be free -> NetApiBufferFree(userinfos);
NET_API_STATUS NetUserGetLocalGroups(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR username, // NULL is current user
std::vector<_bstr_t>& parray); // local group name array
NET_API_STATUS NetUserSetGroups(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR username, // NULL is current user
LPCTSTR groupname); // Not NULL, global groupname
}
namespace GROUP
{
/* Declared in Lmaccess.h; include Lm.h. Use Netapi32.lib.
NetLocalGroupAdd Creates a local group.
NetLocalGroupAddMembers Adds one or more users or global groups to an existing local group.
NetLocalGroupDel Deletes a local group, removing all existing members from the group.
NetLocalGroupDelMembers Removes one or more members from an existing local group.
NetLocalGroupEnum Returns information about each local group account on a server.
NetLocalGroupGetInfo Returns information about a particular local group account on a server.
NetLocalGroupGetMembers Lists all members of a specified local group.
NetLocalGroupSetInfo Sets general information about a local group.
NetLocalGroupSetMembers Assigns members to a local group.
*/
NET_API_STATUS NetLocalGroupAdd(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR groupname, // Not NULL, groupname
LPCTSTR comment); // Allow NULL
NET_API_STATUS NetLocalGroupAddMembers(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR groupname, // Not NULL, groupname
LPCTSTR username); // Not NULL, username
NET_API_STATUS NetLocalGroupDel(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR groupname); // Not NULL, groupname
NET_API_STATUS NetLocalGroupDelMembers(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR groupname, // Not NULL, groupname
LPCTSTR username); // Not NULL, username
NET_API_STATUS NetLocalGroupEnum(
LPCTSTR domainname, // NULL is localcomputer
LPDWORD dwCount, // group count
PLOCALGROUP_INFO_1& groupinfo); // group info array, must be free -> NetApiBufferFree(groupinfo);
NET_API_STATUS NetLocalGroupGetInfo(
LPCTSTR domainname, // NULL is localcomputer
LPCTSTR groupname, // Not NULL, groupname
PLOCALGROUP_INFO_1& groupinfo); // group info, must be free -> NetApiBufferFree(groupinfo);
};
namespace PRIVILEGE
{
// 현재 프로세스에 주어진 권한을 인에이블/디스에이블 시킨다.
BOOL SetProcessPrivilege(
LPCTSTR lpszPrivilege, // privilege to grant
BOOL bEnablePrivilege); // add or remove
// 사용자 계정에 특별한 권한을 추가시킨다.
NTSTATUS SetPrivilegeOnAccount(
LSA_HANDLE PolicyHandle, // open policy handle
PSID AccountSid, // SID to grant privilege to
LPWSTR PrivilegeName, // privilege to grant (Unicode)
BOOL bEnable); // enable or disable
// 해당 유저에게 할당된 부가적인 프리빌리지를 얻어온다.
// 일반적인 프리빌리지를 얻어올 경우는, 해당 유저가 띄운 프로세스를 이용하여
// 아래의 GetPrivilegeFromProcessHandle 함수를 이용하여 가져온다.
NTSTATUS EnumPrivilegeOnAccount(
LSA_HANDLE PolicyHandle, // open policy handle
PSID AccountSid, // SID to grant privilege to
std::vector<_bstr_t>& parray); // privileges array
// 프로세스 핸들을 이용하여 해당 유저 이름을 얻어온다.
BOOL GetUserFromProcessHandle(
LPTSTR AccountName, // account of interest
DWORD* cbName, // string buffer length
HANDLE hProcess = NULL); // process id, NULL is owner process
// 프로세스 아이디를 이용하여 해당 유저 이름을 얻어온다.
BOOL GetUserFromProcessID(
LPTSTR AccountName, // account of interest
DWORD* cbName, // string buffer length
DWORD nProcessID = 0xFFFFFFFF); // process id, 0xFFFFFFFF is owner process
// 프로세스 핸들을 이용하여 해당 프로세스에 설정된 프리빌리지 목록을 얻어온다.
BOOL GetPrivilegeFromProcessHandle(
std::vector<_bstr_t>& parray, // privileges array
HANDLE hProcess = NULL); // process id, NULL is owner process
// 프로세스 아이디을 이용하여 해당 프로세스에 설정된 프리빌리지 목록을 얻어온다.
BOOL GetPrivilegeFromProcessID(
std::vector<_bstr_t>& parray, // privileges array
DWORD nProcessID = 0xFFFFFFFF); // process id, 0xFFFFFFFF is owner process
};
};
작업을 하다보니 4GB 크기가 넘는 파일을 다루어야 할 일이 생겼네요. 윈도우즈 커널에 _hread, _hwrite 등의 숨겨진 API가 제공되지만.. 이런거 찾아다가 처리하기가 여간 귀찬아서.. ㅎㅎ;
대형 파일을 그냥 fopen, open, OpenFile 등등.. 어떤 API로 처리하든지 그냥 읽고 쓰기는 가능합니다. (단, 이 방법을 사용하시더라도 fseek, lseek는 불가능합니다. 파일을 seek해야 할 경우는 처음에 이야기 처럼 kernel에 숨겨진 위의 함수들을 찾아서 사용하시던지, Visual Studio 2005 이상 버전을 사용하세요.