Getting another process command line in Windows - c++

I am trying to get another process' command-line parameters (on WinXP 32bit).
I do the following:
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, ProcList.proc_id_as_numbers[i]);
BytesNeeded = sizeof(PROCESS_BASIC_INFORMATION);
ZwQueryInformationProcess(hProcess, ProcessBasicInformation, UserPool, sizeof(PROCESS_BASIC_INFORMATION), &BytesNeeded);
pbi = (PPROCESS_BASIC_INFORMATION)UserPool;
BytesNeeded = sizeof(PEB);
res = ZwReadVirtualMemory(hProcess, pbi->PebBaseAddress, UserPool, sizeof(PEB), &BytesNeeded);
/* zero value returned */
peb = (PPEB)UserPool;
BytesNeeded = sizeof(RTL_USER_PROCESS_PARAMETERS);
res = ZwReadVirtualMemory(hProcess, peb->ProcessParameters, UserPool, sizeof(RTL_USER_PROCESS_PARAMETERS), &BytesNeeded);
ProcParam = (PRTL_USER_PROCESS_PARAMETERS)UserPool;
After the first call, pbi.UniqueProcessID is correct.
But, after calling ZwReadVirtualMemory(), I get the command-line for my process, not the requested one.
I also used ReadProcessMemory() & NtQueryInformationProcess(), but get the same result.
Can anybody help?
On this forum thread, it is said that this code works. Unfortunately, I do not have access to post on that forum to ask them.

It looks like ZwReadVirtualMemory is called only once. That is not enough. It has to be called for each level of pointer indirection. In other words when you retrieve a pointer it points to other process' address space. You cannot read it directly. You have to call ZwReadVirtualMemory again. For the case of those data structures ZwReadVirtualMemory has to be called 3 times: once to read PEB (that is what the code above does), once to read RTL_USER_PROCESS_PARAMETERS and once to read UNICODE_STRING's buffer.
The following code fragment worked for me (error handling omitted for clarity and I used documented ReadProcessMemory API instead of ZwReadVirtualMemory):
LONG status = NtQueryInformationProcess(hProcess,
0,
pinfo,
sizeof(PVOID)*6,
NULL);
PPEB ppeb = (PPEB)((PVOID*)pinfo)[1];
PPEB ppebCopy = (PPEB)malloc(sizeof(PEB));
BOOL result = ReadProcessMemory(hProcess,
ppeb,
ppebCopy,
sizeof(PEB),
NULL);
PRTL_USER_PROCESS_PARAMETERS pRtlProcParam = ppebCopy->ProcessParameters;
PRTL_USER_PROCESS_PARAMETERS pRtlProcParamCopy =
(PRTL_USER_PROCESS_PARAMETERS)malloc(sizeof(RTL_USER_PROCESS_PARAMETERS));
result = ReadProcessMemory(hProcess,
pRtlProcParam,
pRtlProcParamCopy,
sizeof(RTL_USER_PROCESS_PARAMETERS),
NULL);
PWSTR wBuffer = pRtlProcParamCopy->CommandLine.Buffer;
USHORT len = pRtlProcParamCopy->CommandLine.Length;
PWSTR wBufferCopy = (PWSTR)malloc(len);
result = ReadProcessMemory(hProcess,
wBuffer,
wBufferCopy, // command line goes here
len,
NULL);
Why we see see the command line of our own process? That is because processes are laid out in a similar way. Command line and PEB-related structures are likely to have the same addresses. So if you missed ReadProcessMemory you end up exactly with local process' command line.

I was trying to do this same thing using mingw & Qt. I ran into a problem with "undefined reference to CLSID_WbemLocator". After some research, it seems that the version of libwbemuuid.a which was included with my version of mingw only defined IID_IWbemLocator but not CLSID_WbemLocator.
I found that manually defining CLSID_WbemLocator works (although its probably not the "correct" way of doing things).
The final working code:
#include <QDebug>
#include <QString>
#include <QDir>
#include <QProcess>
#define _WIN32_DCOM
#include <windows.h>
#include "TlHelp32.h"
#include <stdio.h>
#include <tchar.h>
#include <wbemidl.h>
#include <comutil.h>
const GUID CLSID_WbemLocator = { 0x4590F811,0x1D3A,0x11D0,{ 0x89,0x1F,0x00,0xAA,0x00,0x4B,0x2E,0x24 } }; //for some reason CLSID_WbemLocator isn't declared in libwbemuuid.a (although it probably should be).
int getProcessInfo(DWORD pid, QString *commandLine, QString *executable)
{
HRESULT hr = 0;
IWbemLocator *WbemLocator = NULL;
IWbemServices *WbemServices = NULL;
IEnumWbemClassObject *EnumWbem = NULL;
//initializate the Windows security
hr = CoInitializeEx(0, COINIT_MULTITHREADED);
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &WbemLocator);
//connect to the WMI
hr = WbemLocator->ConnectServer(L"ROOT\\CIMV2", NULL, NULL, NULL, 0, NULL, NULL, &WbemServices);
//Run the WQL Query
hr = WbemServices->ExecQuery(L"WQL", L"SELECT ProcessId,CommandLine,ExecutablePath FROM Win32_Process", WBEM_FLAG_FORWARD_ONLY, NULL, &EnumWbem);
qDebug() << "Got here." << (void*)hr;
// Iterate over the enumerator
if (EnumWbem != NULL) {
IWbemClassObject *result = NULL;
ULONG returnedCount = 0;
while((hr = EnumWbem->Next(WBEM_INFINITE, 1, &result, &returnedCount)) == S_OK) {
VARIANT ProcessId;
VARIANT CommandLine;
VARIANT ExecutablePath;
// access the properties
hr = result->Get(L"ProcessId", 0, &ProcessId, 0, 0);
hr = result->Get(L"CommandLine", 0, &CommandLine, 0, 0);
hr = result->Get(L"ExecutablePath", 0, &ExecutablePath, 0, 0);
if (ProcessId.uintVal == pid)
{
*commandLine = QString::fromUtf16((ushort*)(long)CommandLine.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
*executable = QString::fromUtf16((ushort*)(long)ExecutablePath.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
qDebug() << *commandLine << *executable;
}
result->Release();
}
}
// Release the resources
EnumWbem->Release();
WbemServices->Release();
WbemLocator->Release();
CoUninitialize();
//getchar();
return(0);
}
and in my Qt project file (.pro) I link to the following libraries:
LIBS += -lole32 -lwbemuuid

Duplicate of How to query a running process for it's parameters list? (windows, C++) , so I'll just copy my answer from there over here:
You can't reliably get that information. There are various tricks to try and retrieve it, but there's no guarantee that the target process hasn't already mangled that section of memory. Raymond Chen discussed this awhile back on The Old New Thing.

You need to be more disciplined with checking return codes. It may be that any of your ZwReadVirtualMemory calls yield an error code which points you into the right direction.
In particular, the ProcList.proc_id_as_numbers[i] part suggests that you're executing this code in a loop. Chances are that the procPeb.ProcessParameters structure is still filled with the values of an earlier loop iteration - and since the ZwReadVirtualMemory call fails on your target process, you get to see the command line of whatever process was previously queried.

You don't have to read the VM of the target process to do this. Just make sure you have the correct Process ID for the target process.
Once you have the process handle via OpenProcess, you can then use NtQueryInformationProcess to get detailed process info. Use the ProcessBasicInformation option to get the PEB of the process - this contains another structure pointer RTL_USER_PROCESS_PARAMETERS, through which you can get the command line.

Related

How to Identify an windows process with a certain executable and command line argument is running or not using C++? [duplicate]

I am trying to get another process' command-line parameters (on WinXP 32bit).
I do the following:
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, ProcList.proc_id_as_numbers[i]);
BytesNeeded = sizeof(PROCESS_BASIC_INFORMATION);
ZwQueryInformationProcess(hProcess, ProcessBasicInformation, UserPool, sizeof(PROCESS_BASIC_INFORMATION), &BytesNeeded);
pbi = (PPROCESS_BASIC_INFORMATION)UserPool;
BytesNeeded = sizeof(PEB);
res = ZwReadVirtualMemory(hProcess, pbi->PebBaseAddress, UserPool, sizeof(PEB), &BytesNeeded);
/* zero value returned */
peb = (PPEB)UserPool;
BytesNeeded = sizeof(RTL_USER_PROCESS_PARAMETERS);
res = ZwReadVirtualMemory(hProcess, peb->ProcessParameters, UserPool, sizeof(RTL_USER_PROCESS_PARAMETERS), &BytesNeeded);
ProcParam = (PRTL_USER_PROCESS_PARAMETERS)UserPool;
After the first call, pbi.UniqueProcessID is correct.
But, after calling ZwReadVirtualMemory(), I get the command-line for my process, not the requested one.
I also used ReadProcessMemory() & NtQueryInformationProcess(), but get the same result.
Can anybody help?
On this forum thread, it is said that this code works. Unfortunately, I do not have access to post on that forum to ask them.
It looks like ZwReadVirtualMemory is called only once. That is not enough. It has to be called for each level of pointer indirection. In other words when you retrieve a pointer it points to other process' address space. You cannot read it directly. You have to call ZwReadVirtualMemory again. For the case of those data structures ZwReadVirtualMemory has to be called 3 times: once to read PEB (that is what the code above does), once to read RTL_USER_PROCESS_PARAMETERS and once to read UNICODE_STRING's buffer.
The following code fragment worked for me (error handling omitted for clarity and I used documented ReadProcessMemory API instead of ZwReadVirtualMemory):
LONG status = NtQueryInformationProcess(hProcess,
0,
pinfo,
sizeof(PVOID)*6,
NULL);
PPEB ppeb = (PPEB)((PVOID*)pinfo)[1];
PPEB ppebCopy = (PPEB)malloc(sizeof(PEB));
BOOL result = ReadProcessMemory(hProcess,
ppeb,
ppebCopy,
sizeof(PEB),
NULL);
PRTL_USER_PROCESS_PARAMETERS pRtlProcParam = ppebCopy->ProcessParameters;
PRTL_USER_PROCESS_PARAMETERS pRtlProcParamCopy =
(PRTL_USER_PROCESS_PARAMETERS)malloc(sizeof(RTL_USER_PROCESS_PARAMETERS));
result = ReadProcessMemory(hProcess,
pRtlProcParam,
pRtlProcParamCopy,
sizeof(RTL_USER_PROCESS_PARAMETERS),
NULL);
PWSTR wBuffer = pRtlProcParamCopy->CommandLine.Buffer;
USHORT len = pRtlProcParamCopy->CommandLine.Length;
PWSTR wBufferCopy = (PWSTR)malloc(len);
result = ReadProcessMemory(hProcess,
wBuffer,
wBufferCopy, // command line goes here
len,
NULL);
Why we see see the command line of our own process? That is because processes are laid out in a similar way. Command line and PEB-related structures are likely to have the same addresses. So if you missed ReadProcessMemory you end up exactly with local process' command line.
I was trying to do this same thing using mingw & Qt. I ran into a problem with "undefined reference to CLSID_WbemLocator". After some research, it seems that the version of libwbemuuid.a which was included with my version of mingw only defined IID_IWbemLocator but not CLSID_WbemLocator.
I found that manually defining CLSID_WbemLocator works (although its probably not the "correct" way of doing things).
The final working code:
#include <QDebug>
#include <QString>
#include <QDir>
#include <QProcess>
#define _WIN32_DCOM
#include <windows.h>
#include "TlHelp32.h"
#include <stdio.h>
#include <tchar.h>
#include <wbemidl.h>
#include <comutil.h>
const GUID CLSID_WbemLocator = { 0x4590F811,0x1D3A,0x11D0,{ 0x89,0x1F,0x00,0xAA,0x00,0x4B,0x2E,0x24 } }; //for some reason CLSID_WbemLocator isn't declared in libwbemuuid.a (although it probably should be).
int getProcessInfo(DWORD pid, QString *commandLine, QString *executable)
{
HRESULT hr = 0;
IWbemLocator *WbemLocator = NULL;
IWbemServices *WbemServices = NULL;
IEnumWbemClassObject *EnumWbem = NULL;
//initializate the Windows security
hr = CoInitializeEx(0, COINIT_MULTITHREADED);
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &WbemLocator);
//connect to the WMI
hr = WbemLocator->ConnectServer(L"ROOT\\CIMV2", NULL, NULL, NULL, 0, NULL, NULL, &WbemServices);
//Run the WQL Query
hr = WbemServices->ExecQuery(L"WQL", L"SELECT ProcessId,CommandLine,ExecutablePath FROM Win32_Process", WBEM_FLAG_FORWARD_ONLY, NULL, &EnumWbem);
qDebug() << "Got here." << (void*)hr;
// Iterate over the enumerator
if (EnumWbem != NULL) {
IWbemClassObject *result = NULL;
ULONG returnedCount = 0;
while((hr = EnumWbem->Next(WBEM_INFINITE, 1, &result, &returnedCount)) == S_OK) {
VARIANT ProcessId;
VARIANT CommandLine;
VARIANT ExecutablePath;
// access the properties
hr = result->Get(L"ProcessId", 0, &ProcessId, 0, 0);
hr = result->Get(L"CommandLine", 0, &CommandLine, 0, 0);
hr = result->Get(L"ExecutablePath", 0, &ExecutablePath, 0, 0);
if (ProcessId.uintVal == pid)
{
*commandLine = QString::fromUtf16((ushort*)(long)CommandLine.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
*executable = QString::fromUtf16((ushort*)(long)ExecutablePath.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
qDebug() << *commandLine << *executable;
}
result->Release();
}
}
// Release the resources
EnumWbem->Release();
WbemServices->Release();
WbemLocator->Release();
CoUninitialize();
//getchar();
return(0);
}
and in my Qt project file (.pro) I link to the following libraries:
LIBS += -lole32 -lwbemuuid
Duplicate of How to query a running process for it's parameters list? (windows, C++) , so I'll just copy my answer from there over here:
You can't reliably get that information. There are various tricks to try and retrieve it, but there's no guarantee that the target process hasn't already mangled that section of memory. Raymond Chen discussed this awhile back on The Old New Thing.
You need to be more disciplined with checking return codes. It may be that any of your ZwReadVirtualMemory calls yield an error code which points you into the right direction.
In particular, the ProcList.proc_id_as_numbers[i] part suggests that you're executing this code in a loop. Chances are that the procPeb.ProcessParameters structure is still filled with the values of an earlier loop iteration - and since the ZwReadVirtualMemory call fails on your target process, you get to see the command line of whatever process was previously queried.
You don't have to read the VM of the target process to do this. Just make sure you have the correct Process ID for the target process.
Once you have the process handle via OpenProcess, you can then use NtQueryInformationProcess to get detailed process info. Use the ProcessBasicInformation option to get the PEB of the process - this contains another structure pointer RTL_USER_PROCESS_PARAMETERS, through which you can get the command line.

GlobalAlloc instead of other allocation method

While going through code on MSDN for finding the owner of the given file object, the program uses GlobalAlloc() to allocate the memory to AcctName. Since I'm new to using the Win32 API, my curiosity made me enquired why we are using GlobalAlloc() instead of other allocation methods. If other better allocation methods exist, how to use them in the given code?
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include "accctrl.h"
#include "aclapi.h"
#pragma comment(lib, "advapi32.lib")
int main(void)
{
DWORD dwRtnCode = 0;
PSID pSidOwner = NULL;
BOOL bRtnBool = TRUE;
LPTSTR AcctName = NULL;
LPTSTR DomainName = NULL;
DWORD dwAcctName = 1, dwDomainName = 1;
SID_NAME_USE eUse = SidTypeUnknown;
HANDLE hFile;
PSECURITY_DESCRIPTOR pSD = NULL;
// Get the handle of the file object.
hFile = CreateFile(
TEXT("myfile.txt"),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
// Check GetLastError for CreateFile error code.
if (hFile == INVALID_HANDLE_VALUE) {
DWORD dwErrorCode = 0;
dwErrorCode = GetLastError();
_tprintf(TEXT("CreateFile error = %d\n"), dwErrorCode);
return -1;
}
// Get the owner SID of the file.
dwRtnCode = GetSecurityInfo(
hFile,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION,
&pSidOwner,
NULL,
NULL,
NULL,
&pSD);
// Check GetLastError for GetSecurityInfo error condition.
if (dwRtnCode != ERROR_SUCCESS) {
DWORD dwErrorCode = 0;
dwErrorCode = GetLastError();
_tprintf(TEXT("GetSecurityInfo error = %d\n"), dwErrorCode);
return -1;
}
// First call to LookupAccountSid to get the buffer sizes.
bRtnBool = LookupAccountSid(
NULL, // local computer
pSidOwner,
AcctName,
(LPDWORD)&dwAcctName,
DomainName,
(LPDWORD)&dwDomainName,
&eUse);
// Reallocate memory for the buffers.
AcctName = (LPTSTR)GlobalAlloc(
GMEM_FIXED,
dwAcctName);
// Check GetLastError for GlobalAlloc error condition.
if (AcctName == NULL) {
DWORD dwErrorCode = 0;
dwErrorCode = GetLastError();
_tprintf(TEXT("GlobalAlloc error = %d\n"), dwErrorCode);
return -1;
}
DomainName = (LPTSTR)GlobalAlloc(
GMEM_FIXED,
dwDomainName);
// Check GetLastError for GlobalAlloc error condition.
if (DomainName == NULL) {
DWORD dwErrorCode = 0;
dwErrorCode = GetLastError();
_tprintf(TEXT("GlobalAlloc error = %d\n"), dwErrorCode);
return -1;
}
// Second call to LookupAccountSid to get the account name.
bRtnBool = LookupAccountSid(
NULL, // name of local or remote computer
pSidOwner, // security identifier
AcctName, // account name buffer
(LPDWORD)&dwAcctName, // size of account name buffer
DomainName, // domain name
(LPDWORD)&dwDomainName, // size of domain name buffer
&eUse); // SID type
// Check GetLastError for LookupAccountSid error condition.
if (bRtnBool == FALSE) {
DWORD dwErrorCode = 0;
dwErrorCode = GetLastError();
if (dwErrorCode == ERROR_NONE_MAPPED)
_tprintf(TEXT
("Account owner not found for specified SID.\n"));
else
_tprintf(TEXT("Error in LookupAccountSid.\n"));
return -1;
} else if (bRtnBool == TRUE)
// Print the account name.
_tprintf(TEXT("Account owner = %s\n"), AcctName);
return 0;
}
See this Microsoft documentation page for an overview of the different memory allocation functions that currently exist in Microsoft Windows.
Basically, GlobalAlloc is a remnant of 16-bit Windows. In 32-bit and 64-bit Windows, there is no longer a distinction between GlobalAlloc and LocalAlloc, as modern operating systems use a flat memory model. If you are interested in what significance these functions had in 16-bit Windows, you can read this article by Microsoft blogger Raymond Chen.
As stated in the offical documentation of the function GlobalAlloc, this function should generally not be used in modern 32-bit or 64-bit applications, except in situations where the documentation specifically tells you to do so.
Since the official documentation on the function LookupAccountSid does not specify that any of the passed buffers must have been allocated with GlobalAlloc, there is no reason to use this function. It would be more appropriate to for example use the function HeapAlloc, as this function has lower overhead than GlobalAlloc. Alternatively, you can use normal C++ memory allocations (for example new or std::vector), which, depending on the implementation, probably calls the function HeapAlloc indirectly.
Thanks for everyone.
I have just modified my code by using malloc.
AccountBuff = (LPSTR)malloc(AccountBufflength * sizeof(LPSTR));
DomainBuff = (LPSTR)malloc(DomainBufflength * sizeof(LPSTR));

IPersistFile::Save method keeps failing to save the shortcut

I am trying to save a shortcut to my application in the startup folder. It all compiles, but it fails to actually save the game. The error seems to occur at hres = ppf->Save(wsz, TRUE);, where hres is set to -2147024891. If that means something specific, I haven't discovered what yet. My code is copied almost verbatim from MSDN, so I'm pretty confused why it isn't working. Perhaps I don't have permission to save a shortcut to the startup folder? Then again, I am also fairly new to all this, so it might be some basic error I am making. I am copying in all my #includes as well in case that is the problem.
Edit:
First, to avoid confusion, this is CLI based C++.
Checking hres for errors is just part of the MDSN code. This is really almost the exact same code from the website example. I have put in breakpoints, which is how I know that hres becomes -2147024891 right after the line hres = ppf->Save(wsz, TRUE); is run.
In case these are wrong, mediaMaestroLocation is set to "C:\Users\Keith\Documents\Visual Studio 2012\Projects\MediaMaestro\Debug\MediaMaestro.exe" and startupDestination is "C:\Users\Keith\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup". While the exe location looks great, I wonder if it matters that there isn't a \ after the destination folder path. I would have checked it already, but I need to spend a couple minutes figure out how to do it first.
#include <windows.h>
#include <string>
#include <stdio.h>
#include <shobjidl.h>
#include <shlobj.h>
#include "objbase.h"
#include <objidl.h>
#include <shlguid.h>
#include <winnls.h>
#using <System.dll>
#using <System.Windows.Forms.dll>
using namespace System;
using namespace System::Windows::Forms;
char startupDestination[MAX_PATH];
char mediaMaestroLocation[MAX_PATH];
DWORD nChars = 0;
BOOL yChars = 0;
HRESULT CreateLink()
{
CoInitializeEx( NULL, 0 );
HRESULT hres = 0;
IShellLink* psl;
if (SUCCEEDED(hres))
{
// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
// has already been called.
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if (SUCCEEDED(hres))
{
IPersistFile* ppf;
// Set the path to the shortcut target and add the description.
psl->SetPath(mediaMaestroLocation);
psl->SetDescription("Media Maestro");
// Query IShellLink for the IPersistFile interface, used for saving the
// shortcut in persistent storage.
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if (SUCCEEDED(hres))
{
WCHAR wsz[MAX_PATH];
// Ensure that the string is Unicode.
MultiByteToWideChar(CP_ACP, 0, startupDestination, -1, wsz, MAX_PATH);
// Add code here to check return value from MultiByteWideChar
// for success.
// Save the link by calling IPersistFile::Save.
hres = ppf->Save(wsz, TRUE);
ppf->Release();
}
psl->Release();
}
}
CoUninitialize();
return hres;
}
Here is the click event in the UI that calls the function:
void settingsLaunchOnStart_Click( Object^ Sender, EventArgs^ e )
{
if (settingsLaunchOnStart->Checked == false)
{
HRESULT r;
nChars = GetModuleFileName( NULL, mediaMaestroLocation, sizeof(mediaMaestroLocation) );
yChars = SHGetFolderPath( NULL, CSIDL_STARTUP, NULL, SHGFP_TYPE_CURRENT, startupDestination);
r = CreateLink();
}
else if (settingsLaunchOnStart->Checked == true)
{
//code to remove the shortcut
}
}
Is there something I am missing?
It turns out that it wasn't enough to name the output folder path, I had to name the file and extension as well. It seems strange to me, considering I don't think I have seen a single other example doing this. Anyway, here is my updated working code:
HRESULT CreateLink()
{
CoInitializeEx( NULL, 0 );
HRESULT hres = 0;
IShellLink* psl;
if (SUCCEEDED(hres))
{
// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
// has already been called.
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_ALL, IID_IShellLink, (LPVOID*)&psl); //CLSCTX_ALL CLSCTX_INPROC_SERVER (void**)&psl (LPVOID*)&psl
if (SUCCEEDED(hres))
{
IPersistFile* ppf;
// Set the path to the shortcut target and add the description.
psl->SetPath(mediaMaestroLocation);
psl->SetDescription(L"Media Maestro");
psl->SetIconLocation(mediaMaestroLocation, 0);
// Query IShellLink for the IPersistFile interface, used for saving the
// shortcut in persistent storage.
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); //(void**)&psl (LPVOID*)&ppf
if (SUCCEEDED(hres))
{
WCHAR wsz[MAX_PATH];
// Save the link by calling IPersistFile::Save.
hres = _wmakepath_s( wsz, _MAX_PATH, NULL, startupDestination,
L"MediaMaestro", L"lnk" );
hres = ppf->Save(wsz, TRUE);
ppf->Release();
}
psl->Release();
}
}
CoUninitialize();
return hres;
}
The addition of _wmakepath_s lets me append the name of my program and its extension onto the filepath I got from SHGetFolderPath. Once I feed that into the IPersistFile interface it saves as it should.
you have only initialized hres to 0 and then ur checking if it succeded ? u are never realling declaring it somewhere, and -2147024891 probably means that the variable is not initialized yet.
a wild guess is that it never even reahes the: hres = ppf->Save(wsz, TRUE); line and therefore it is not initialized :P try putting out some breakpoints when debugging and maybe use some watches to peek into variables :)
Best regards.

How to use the "__ProviderArchitecture" flag of IWbemContext in c?

I've been trying to follow the following MSDN tutorial to query the 64 bit registry provider from a 32 bit application.
Sadly, the examples are all written in VB, and I'm stuck with something.
For C++ developers, the article mentions that...
C++ applications can use the IWbemContext interface with IWbemServices::ExecMethod to communicate the use of a nondefault provider to WMI.
...although, when you look at the sample VB code, the context object is also used in the ConnectServer method:
Set objCtx = CreateObject("WbemScripting.SWbemNamedValueSet")
objCtx.Add "__ProviderArchitecture", 32
Set objLocator = CreateObject("Wbemscripting.SWbemLocator")
Set objServices = objLocator.ConnectServer("","root\default","","",,,,objCtx)
Set objStdRegProv = objServices.Get("StdRegProv")
I've tried reproducing this in VC++:
HRESULT res;
CComPtr<IWbemContext> ctx;
if (!create_registry_redirection_context_(hive, ctx)) {
return false;
}
res = locator_->ConnectServer(CComBSTR(namespace_.c_str()), // Namespace to use
0, // Current security context (username)
0, // Current security context (password)
0, // Use current locale
WBEM_FLAG_CONNECT_USE_MAX_WAIT, // Return if connexion is unsuccessful after 2 minutes
0, // Name of the domain of the user to authenticate
ctx, // Optional context
&service_); // Fill this pointer
The create_registry_redirection_context_ method uses CoCreateInstance to instantiate my context, and I use the following lines to set the architecture:
CComVariant value_arch(64, VT_I4);
ctx->SetValue(L"__ProviderArchitecture", 0, &value_arch);
Now the problem is, the ConnectServer method returns an error (0x80041008 - WMI Invalid Parameter). If I comment out the ctx->SetValue(L"__ProviderArchitecture", 0, &value_arch); line, everything works properly, but of course, I end up querying the 32 bit registry provider.
I've also tried not setting any context during the ConnectServer call, but only during the ExecMethod call (as specified in the MSDN article), but although I don't get any error, I'm still querying the 32 bit provider, and not seeing my 64bit registry keys.
What am I doing wrong?
Thanks in advance for your time.
I know, it's a little bit late, but for archive (and because MS is unable to provide such a sample):
HRESULT hres;
IWbemLocator *pLoc = NULL;
hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc);
if (FAILED(hres))
{
m_nExitCode = TCP1;
return FALSE;
}
IWbemContext *pContext = NULL;
hres = CoCreateInstance(CLSID_WbemContext, 0, CLSCTX_INPROC_SERVER, IID_IWbemContext, (LPVOID *) &pContext);
if (FAILED(hres))
{
m_nExitCode = TCP1_2;
return FALSE;
}
VARIANT vArchitecture;
VariantInit(&vArchitecture);
V_VT(&vArchitecture) = VT_I4;
V_INT(&vArchitecture) = 64;
hres = pContext->SetValue(_bstr_t(L"__ProviderArchitecture"), 0, &vArchitecture);
VariantClear(&vArchitecture);
//VARIANT vRequiredArchicture;
//VariantInit(&vRequiredArchicture);
//V_VT(&vRequiredArchicture) = VT_BOOL;
//V_BOOL(&vRequiredArchicture) = VARIANT_TRUE;
//hres = pContext->SetValue(_bstr_t(L"__RequiredArchitecture"), 0, &vRequiredArchicture);
//VariantClear(&vRequiredArchicture);
IWbemServices *pSvc = NULL;
hres = pLoc->ConnectServer(
_bstr_t(L"root\\Microsoft\\SqlServer\\ComputerManagement10"), // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (for example, Kerberos)
pContext, // Context object
&pSvc // pointer to IWbemServices proxy
);
The block with RequiredArchitecture is untested and seems to be unnecessary.
PS: Error-handling needs to be improved!
have you looked at
http://msdn.microsoft.com/en-us/library/windows/desktop/aa393067(v=vs.85).aspx
and tried setting "__RequiredArchitecture" = TRUE as well?
Also have to tried to get it to work in the way that the same code shows ( ie 64 bit to 32 bit) first?

Descriptive monitor name from D3D display adapter ID

As the question suggests, I'm trying to pull a descriptive monitor name to match with a display adapter name. The code below gives me a device ID like \.\DISPLAY1 which is understandable but not what I'm looking for.
// Get name.
D3DADAPTER_IDENTIFIER9 d3dID;
d3d9.Get().GetAdapterIdentifier(iAdapter, 0, &d3dID);
dispAd.name = d3dID.Description;
// Add monitor ID to display adapter name.
FIX_ME // Not happy with this yet!
HMONITOR hMonitor = d3d9.Get().GetAdapterMonitor(iAdapter);
MONITORINFOEXA monInfoEx;
monInfoEx.cbSize = sizeof(MONITORINFOEXA);
if (GetMonitorInfoA(hMonitor, &monInfoEx))
{
dispAd.name = dispAd.name + " on: " + monInfoEx.szDevice;
}
else TPB_ASSERT(0); // Mute?
I've looked around the documentation for where to pull that actual name from but until now I haven't been able to find it. Sometimes I am a little stupid (or blind if you will), so I'll give it another go during my lunch break -- but perhaps someone can point me in the right direction? Thanks a lot.
(and by actual name I mean the one presented in the graphics configuration panel)
UINT iOutput = 0;
IDXGIOutput *pOutput = nullptr;
while (DXGI_ERROR_NOT_FOUND != pAdapter->EnumOutputs(iOutput++, &pOutput))
{
DXGI_OUTPUT_DESC desc;
VERIFY(S_OK == pOutput->GetDesc(&desc));
MONITORINFOEXW monInfoEx;
monInfoEx.cbSize = sizeof(MONITORINFOEXW);
GetMonitorInfoW(desc.Monitor, &monInfoEx);
DISPLAY_DEVICEW dispDev;
dispDev.cb = sizeof(DISPLAY_DEVICEW);
EnumDisplayDevicesW(monInfoEx.szDevice, 0, &dispDev, 0);
// FIXME: far from perfect, but should do the job if a vendor driver is installed.
// Otherwise it just displays something along the lines of "Plug & Play monitor".
SendDlgItemMessageW(hDialog, IDC_COMBO_OUTPUT, CB_ADDSTRING, 0, (LPARAM) dispDev.DeviceString);
pOutput->Release();
}
This works. It is supposed to need only Windows+stl to compile and eats HMONITOR. Some things I'm not happy with:
The WMI stuff assuming the monitor order is the same as EnumDisplayDevices(). I wanted to compare to the ID string but could not find it in the WMI data. Still needs another look.
The WMI code probably doesn't use the optimal name field but on the Netbook I have around right now all of them say "Plug & play" blabla so I'll have to test it on another system a soon as I get the chance. Just a matter of tuning this line in the WMI function, though:
pClassObj->Get(L"Description", 0, &varProp, NULL, NULL);
Code:
// tpbds -- Windows monitor description
// Disable warnings about non-unwindable objects in case C++ exceptions are disabled.
#pragma warning(disable:4530)
// Force Unicode.
#ifndef _UNICODE
#define _UNICODE
#endif
#define _WIN32_DCOM
#pragma comment(lib, "wbemuuid.lib")
#include <windows.h>
#include <comdef.h>
#include <wbemidl.h>
#include <string>
#include <sstream>
#include "monitordescription.h"
#define FIX_ME
#define SAFE_RELEASE(pX) if (pX) pX->Release(); pX = NULL;
// serialize constant value T to std::wstring
template<typename T> inline std::wstring ToWideString(const T &X)
{
std::wstringstream stream;
stream << X;
return stream.str();
}
static const std::wstring GetMonitorDescriptonFromWMI(DWORD iMonitor)
{
// If anything fails down the line I just return an empty string and apply a fallback mechanism.
// This type of function should never fail unless you're probing a non-existent piece of harwdare.
// Initialize COM.
if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
{
return L"";
}
// Set COM security levels.
// Note: if you are using Windows 200, you need to specify the default authentication
// credentials for a user by using a SOLE_AUTHENTICATION_LIST structure in the pAuthList parameter.
if (FAILED(CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL, // pAuthList
EOAC_NONE,
NULL)))
{
CoUninitialize();
return L"";
}
// Obtain initial locator to WMI.
IWbemLocator *pLocator = NULL;
if (FAILED(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast<LPVOID *>(&pLocator))))
{
CoUninitialize();
return L"";
}
// Connect to WMI.
IWbemServices *pServices = NULL;
if (FAILED(pLocator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, NULL, NULL, NULL, NULL, &pServices)))
{
pLocator->Release();
CoUninitialize();
return NULL;
}
// Set security levels on the proxy.
if (FAILED(CoSetProxyBlanket(
pServices,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
NULL,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE)))
{
pServices->Release();
pLocator->Release();
CoUninitialize();
return L"";
}
// Request WMI data.
IEnumWbemClassObject* pEnumerator = NULL;
if (FAILED(pServices->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Win32_DesktopMonitor"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator)))
{
pServices->Release();
pLocator->Release();
CoUninitialize();
return L"";
}
// Try to compile a correct description.
std::wstring description;
DWORD iLoop = 1; // Monitor index is 1-based.
IWbemClassObject *pClassObj = NULL;
while (pEnumerator != NULL)
{
ULONG uReturn = 0;
const HRESULT hRes = pEnumerator->Next(WBEM_INFINITE, 1, &pClassObj, &uReturn);
if (uReturn == 0)
{
// Done (pClassObj remains NULL).
break;
}
// Is this the one we're looking for?
FIX_ME // Untested shortcut (assumes order is identical to that of EnumDisplayDevices).
if (iMonitor == iLoop)
{
FIX_ME // This needs to be tested, I only had a Netbook without proper driver!
VARIANT varProp;
pClassObj->Get(L"Description", 0, &varProp, NULL, NULL); // Check the MSDN for Win32_DesktopMonitor to see what your options are!
description = varProp.bstrVal;
description += L" #" + ToWideString(iMonitor);
VariantClear(&varProp);
SAFE_RELEASE(pClassObj);
// Done.
break;
}
else
SAFE_RELEASE(pClassObj);
}
pServices->Release();
pLocator->Release();
CoUninitialize();
// With a bit of luck this string was just built.
return description;
}
const std::wstring GetMonitorDescription(HMONITOR hMonitor)
{
MONITORINFOEX monInfoEx;
monInfoEx.cbSize = sizeof(MONITORINFOEX);
if (GetMonitorInfo(hMonitor, &monInfoEx))
{
// Get monitor index by matching ID.
DWORD iDevNum = 0;
DISPLAY_DEVICE dispDev;
do
{
dispDev.cb = sizeof(DISPLAY_DEVICE);
EnumDisplayDevices(NULL, iDevNum, &dispDev, 0);
++iDevNum; // Incrementing here is right since we want a 1-based display.
}
while (0 != wcscmp(dispDev.DeviceName, monInfoEx.szDevice));
// Attempt to get the description from WMI.
// If it's empty, carry on.
const std::wstring descriptionFromWMI = GetMonitorDescriptonFromWMI(iDevNum);
if (!descriptionFromWMI.empty())
return descriptionFromWMI;
// Enumerate again, since doing it by string instead of index yields a different (more usable) DeviceString.
dispDev.cb = sizeof(DISPLAY_DEVICE);
EnumDisplayDevices(monInfoEx.szDevice, 0, &dispDev, 0);
// WMI approach failed so we rely on EnumDisplayDevices() for an acceptable result.
std::wstring description(dispDev.DeviceString);
return description + L" #" + ToWideString(iDevNum);
}
else return L"Unknown monitor";
}