안녕, 여러분
이번 글에서는 로컬 또는 원격에서 실행이 가능한 응용프로그램으로 Windows 시스템의 소프트웨어 및 하드웨어 사양에 대한 쿼리에 대한 피드백을 제공하도록 설계된 Windows API 인 WMI의 깊은 곳을 파헤치는 방법에 대한 몇 가지 정보를 소개 할까 합니다.
이 정보들은 설치 윈도우 소프트웨어 버전과 실행 중 프로세서 수와 같은 단순한 정보에서 쿨러의 팬(CPU를 냉각시키는 팬이 있는 경우)의 사이클 수와 같은 매우 세부적인 것까지 포함됩니다.
WMI는 거대한 데이터베이스 시스템처럼 작동하도록 설계되었습니다. 이러한 시스템에는 데이터베이스, 테이블 및 행이 포함됩니다. 또한 WMI가 제공하는 것이기도 합니다. SQL과 같은 구문을 사용하여 테이블에서 개체를 선택할 수 있습니다.
예를 들어 다음은 적절한 하나의 WMI 질의문 입니다:
SELECT * FROM Win32_Session;
WMI에서 데이터베이스는 데이터베이스가 아니라 네임스페이스라고 합니다. 우리의 시스템에는 사용 할 수 있는 다양한 네임스페이스가 존재 합니다. 그 중, 아주 흥미로운 테이블 하나가 바로 네임스페이스 "CIMV2"입니다.
우리는 먼저 여느 데이터베이스와 마찬가지로 연결해야 합니다. 이 작업은 IWbemLocator 인터페이스를 사용하여 만들 수 있습니다. 이 인터페이스(및 모든 후속 인터페이스)는 WbemCli.h 헤더 파일에 정의되어 있습니다. 이러한 인터페이스의 CLSID는 wbemuuid.lib에 정의되어 있습니다.
따라서 첫 번째 코드 조각을 만들어 볼 수 있습니다:
#include <Windows.h>
#include <iostream>
#include <WbemCli.h>
#pragma comment(lib, "wbemuuid.lib")
int main()
{
using std::cout;
using std::cin;
using std::endl;
HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(hRes))
{
cout << "Unable to launch COM: 0x" << std::hex << hRes << endl;
return 1;
}
IWbemLocator* pLocator = NULL;
if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
{
cout << "Unable to create a WbemLocator: " << std::hex << hRes << endl;
return 1;
}
pLocator->Release();
return 0;
}
COM 관련 내용일 뿐 특별한 것은 없습니다.
이제 IWbemLocator 인터페이스에서 ConnectServer라는 메서드를 활용 할 수 있습니다. 이 함수는 네임스페이스를 연결합니다. 우리는 CIMV2를 연결하여 사용 할 것입니다.
해당 함수의 결과로 IWbemServices라는 인터페이스를 얻습니다. 데이터베이스 연결을 열었다라고 생각 해 볼 수 있습니다. 위에서 말했듯이 또한 우리는 다른 시스템의 네임스페이스에 연결할 수도 있습니다.
따라서 ConnectServer는 사용자 이름, 암호 및 연결 권한도 받습니다. 우리는 로컬 컴퓨터에서 현재 (로그 인) 사용자를 사용할 것이기 때문에 우리는 이를 제공할 필요가 없으며 모두 NULL을 전달하면 되고, 네임스페이스를 검색해야 하는 위치로 루트를 설정하여 줍니다.
중요한 점은 WBEM_FLAG_CONNECT_USE_MAX_WAIT를 플래그를 지정해서 MAX_WAIT 시간이 지난 후 함수를 종료 한다는 것을 명시 합니다. 그래야 예를 들어 원격 호스트가 준비되지 않은 경우 함수가 반환되지 않도록 방지할 수 있습니다.
다음은 몇 가지 추가 코드입니다:
#include <Windows.h>
#include <iostream>
#include <WbemCli.h>
#pragma comment(lib, "wbemuuid.lib")
int main()
{
using std::cout;
using std::cin;
using std::endl;
HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(hRes))
{
cout << "Unable to launch COM: 0x" << std::hex << hRes << endl;
return 1;
}
IWbemLocator* pLocator = NULL;
if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
{
cout << "Unable to create a WbemLocator: " << std::hex << hRes << endl;
return 1;
}
IWbemServices* pService = NULL;
if(FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService)))
{
pLocator->Release();
cout << "Unable to connect to \"CIMV2\": " << std::hex << hRes << endl;
return 1;
}
pService->Release();
pLocator->Release();
return 0;
}
이제 CIMV2 네임스페이스에서 정보를 질의할 준비가 되었습니다! 질의는 IWbemService 인터페이스의 ExecQuery 함수를 사용하면 됩니다.
이 함수는 쿼리와 일치하는 모든 개체를 열거하는 enumerator를 반환합니다. 이 enumerator는 양방향 열거자가 아닌 경우 (질의) 속도가 높습니다.
따라서 필요하지 않은 경우 ExecQuery에서 지정하는 것이 좋습니다. ExecQuery의 첫 번째 매개 변수는 이론적으로 쿼리에 사용되는 언어 유형을 지정할 수 있도록 되어 있지만 현재 유일한 유효한 값은 WQL 뿐 입니다.
그래서 다음 코드를 사용 합니다:
#include <Windows.h>
#include <iostream>
#include <WbemCli.h>
#pragma comment(lib, "wbemuuid.lib")
int main()
{
using std::cout;
using std::cin;
using std::endl;
HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(hRes))
{
cout << "Unable to launch COM: 0x" << std::hex << hRes << endl;
return 1;
}
IWbemLocator* pLocator = NULL;
if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
{
cout << "Unable to create a WbemLocator: " << std::hex << hRes << endl;
return 1;
}
IWbemServices* pService = NULL;
if(FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService)))
{
pLocator->Release();
cout << "Unable to connect to \"CIMV2\": " << std::hex << hRes << endl;
return 1;
}
IEnumWbemClassObject* pEnumerator = NULL;
if(FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_DesktopMonitor", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator)))
{
pLocator->Release();
pService->Release();
cout << "Unable to retrive desktop monitors: " << std::hex << hRes << endl;
return 1;
}
pEnumerator->Release();
pService->Release();
pLocator->Release();
return 0;
}
해당 코드를 컴파일하고 실행하면 거의 일반적으로 'Unable to retrieve desktop monitors' 같은 메시지를 표출 할 가능성이 큽니다. 왜냐하면 모든 객체들이 보안 수준으로 캡슐화 되었을 것이기 때문입니다.
현재 우리의 애플리케이션은 어떠한 보안 수준도 선택하지 않았기 때문에 가장 낮은 수준으로 할당됩니다. 이 계층에서 우리는 해당 객체에 접근할 수 없는 것입니다! 이 객체에 접근이 가능하도록 만들기
보안 수준에 대한 전체 설명은 MSDN을 참조하십시오(이에 대한 글을 만들어 볼 때까지는).
현재 작업( 및 가장 일반적인 작업)에 대해 다음 코드로도 충분하다고 믿습니다:
CoInitializeSecurity(NULL, -1, NULL, NULL,
RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0);
CoInitialize 후에 이 함수를 추가하면 위에서 발생한 오류가 제거됩니다! 즉, 이제 객체를 열거할 준비가 되었다는 말이죠!
enumerator는 다루기가 매우 쉽습니다. IEnumWbemClassObjects는 Next라는 메서드를 활용해 봅시다. 이 메서드는 컬렉션에서 객체 덩어리를 요청하고 추출 된 객체의 수를 반환합니다.
또한 여기에서 이러한 객체들이 다른 컴퓨터에 있을 수도 있다는 것을 잊지 마십시오. 따라서 이런 경우도 포함하기 위해서는 Next는 개체가 응답할 때까지 기다려야 하는 시간을 나타내는 매개 변수도 넣어주어야 합니다.
현재는 수행하는 컴퓨터가 원격이 아닌 로컬이기 때문에 INFINITE를 지정합니다.
그러면 다음 코드를 만들어 볼 수 있습니다:
#include <Windows.h>
#include <iostream>
#include <WbemCli.h>
#pragma comment(lib, "wbemuuid.lib")
int main()
{
using std::cout;
using std::cin;
using std::endl;
HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(hRes))
{
cout << "Unable to launch COM: 0x" << std::hex << hRes << endl;
return 1;
}
if((FAILED(hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0))))
{
cout << "Unable to initialize security: 0x" << std::hex << hRes << endl;
return 1;
}
IWbemLocator* pLocator = NULL;
if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
{
cout << "Unable to create a WbemLocator: " << std::hex << hRes << endl;
return 1;
}
IWbemServices* pService = NULL;
if(FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService)))
{
pLocator->Release();
cout << "Unable to connect to \"CIMV2\": " << std::hex << hRes << endl;
return 1;
}
IEnumWbemClassObject* pEnumerator = NULL;
if(FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_DesktopMonitor", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator)))
{
pLocator->Release();
pService->Release();
cout << "Unable to retrive desktop monitors: " << std::hex << hRes << endl;
return 1;
}
IWbemClassObject* clsObj = NULL;
int numElems;
while((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE)
{
if(FAILED(hRes))
break;
clsObj->Release();
}
pEnumerator->Release();
pService->Release();
pLocator->Release();
return 0;
}
이제 마지막으로 선택한 객체의 속성을 가져오는 IWbemClassObject가 있습니다. 이 클래스는 Get 함수를 사용하여 속성을 가져 올 수 있으며, 사용법은 꽤 간단합니다!
이 예에서는 모니터의 속성을 질의 하는 코드 입니다:
#include <Windows.h>
#include <iostream>
#include <WbemCli.h>
#pragma comment(lib, "wbemuuid.lib")
int main()
{
using std::cout;
using std::cin;
using std::endl;
HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(hRes))
{
cout << "Unable to launch COM: 0x" << std::hex << hRes << endl;
return 1;
}
if((FAILED(hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0))))
{
cout << "Unable to initialize security: 0x" << std::hex << hRes << endl;
return 1;
}
IWbemLocator* pLocator = NULL;
if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
{
cout << "Unable to create a WbemLocator: " << std::hex << hRes << endl;
return 1;
}
IWbemServices* pService = NULL;
if(FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService)))
{
pLocator->Release();
cout << "Unable to connect to \"CIMV2\": " << std::hex << hRes << endl;
return 1;
}
IEnumWbemClassObject* pEnumerator = NULL;
if(FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_DesktopMonitor", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator)))
{
pLocator->Release();
pService->Release();
cout << "Unable to retrive desktop monitors: " << std::hex << hRes << endl;
return 1;
}
IWbemClassObject* clsObj = NULL;
int numElems;
while((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE)
{
if(FAILED(hRes))
break;
VARIANT vRet;
VariantInit(&vRet);
if(SUCCEEDED(clsObj->Get(L"Description", 0, &vRet, NULL, NULL)) && vRet.vt == VT_BSTR)
{
std::wcout << L"Description: " << vRet.bstrVal << endl;
VariantClear(&vRet);
}
clsObj->Release();
}
pEnumerator->Release();
pService->Release();
pLocator->Release();
return 0;
}
코드가 완성 되었습니다. 우리는 이제 WMI 내 우리의 첫 번째 객체에 성공적으로 접근했습니다!
사용 가능한 전체 클래스 목록은 다음 링크를 참조하십시오:
MSDN
다음 부분에서는 해당 객체 내에서 메서드를 호출하는 방법을 살펴보겠습니다!
이상.
'프로그래밍' 카테고리의 다른 글
WMI C++ 응용프로그램 예제들 (0) | 2023.02.11 |
---|---|
WMI 데이터 수집 프로그램 예제 (1) | 2023.02.10 |
[C#] 텍스트 파일 읽기 (0) | 2023.02.06 |
[C#.NET]여러가지 머신레벨의 시스템 정보 얻는 방법 (0) | 2023.02.05 |
[C#.NET] 모든 WMI 클래스 속성 찾아내기 (0) | 2023.02.04 |