I'm surprised there's so few examples on this out there. I basically need to do a user/pass authentication against Active Directory. I'm able to initialize a connection to active directory, but it always gives me an "Invalid Credentials" error. I'm wondering if I'm passing it something wrong. This is my first attempt with LDAP. For that matter, I'm open to another (well documented perhaps) solution.
#include <iostream>
#include <stdio.h>
#define LDAP_SERVER "ldap://hq.mydomain.local/"
#include <ldap.h>
int main( int argc, char** argv )
{
LDAP *ld;
int version( LDAP_VERSION3 );
int rc;
char bind_dn[100];
berval creds;
berval* serverCreds;
if( ldap_initialize( &ld, LDAP_SERVER ) ) {
std::cerr << "Error initializing ldap..." << std::endl;
return 1;
}
ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
creds.bv_val = "password";
creds.bv_len = strlen("password");
rc = ldap_sasl_bind_s( ld, "sAMAccountName=MYDOMAIN\\UserName,dc=mydomain,dc=local", "GSSAPI", &creds, NULL, NULL, &serverCreds );
if ( rc != LDAP_SUCCESS ) {
std::cerr << "ERROR: " << ldap_err2string( rc ) << std::endl;
return 1;
} else {
std::cout << "Success." << std::endl;
}
return 0;
}
EDIT:
I wanted to make sure everything is okay on the server side, so did some tests with ldapsearch. It did not work at first, but I finally got it (with ldapsearch, anyway).
ldapsearch -D first.last#mydomain.local -H "ldap://hq.mydomain.local:389" -b "ou=Development,ou=Domain Users,dc=mydomain,dc=local" -W "sAMAccountName=first.last"
Maybe not the best way. For starters, the key is the -D argument, and passing sAMAccountName at the end. I'm not going to have the common name - only the windows login name, and their password. The above command will show a user his info, if the password passes.
The caveat (I think) is that ldap_sasl_bind_s() has no equivalent of setting the -D (binddn) flag. Looking at this question/answer it looks like ldap_interactive_bind_s() might, but it's a bit more involved, as I have to pass a call back.
With the example above where I set a password, but no binddn/username of any kind, who does it assume I'm trying to authenticate as?
sAMAccountName=MYDOMAIN\UserName,dc=mydomain,dc=local
This format of username is incorrect. You do not need to specify sAMAccountName in your username and don't need to specify dc unless you're using Distinguished Name. You have few options for username.
Distinguished Name
CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=Com
sAMaccountName
jsmith
The user path from a previous version of Windows
"Fabrikam\jeffsmith".
User Principal Name (UPN)
jeffsmith#Fabrikam.com
Having said that, I'm not certain if the username is the only issue you're experiencing. I have not run your code locally.
Although this answer may not directly answer your question, since I have not tested this code in Linux machine, it could give you an idea or put you in a right direction. I will not be surprised if this method is Windows specific only.
According to MSDN there're few methods you can use to authenticate a user.
The ADsOpenObject function binds to an ADSI object using explicit user name and password credentials.
This method is accepting the following parameters:
HRESULT ADsOpenObject(
_In_ LPCWSTR lpszPathName,
_In_ LPCWSTR lpszUserName,
_In_ LPCWSTR lpszPassword,
_In_ DWORD dwReserved,
_In_ REFIID riid,
_Out_ VOID **ppObject
);
Using this method you can bind to object in Active Directory by specifying username and password.
If the bind is successful, the return code is S_OK, otherwise you'll get different error messages.
I don't write programs in C++ on a daily basis. I typically work with Active Directory and Active Directory Lightweight Services in a C# world. But this sample code I wrote, shows you how to call ADsOpenObject method to bind to an ADSI object using specified credentials. In your case, just authenticate.
#include <iostream>
#include "activeds.h"
using namespace std;
int main(int argc, char* argv[])
{
HRESULT hr;
IADsContainer *pCont;
IDispatch *pDisp = NULL;
IADs *pUser;
CoInitialize(NULL);
hr = ADsOpenObject( L"LDAP://yourserver",
L"username",
L"password",
ADS_FAST_BIND, //authentication option
IID_IADs,
(void**) &pUser);
if (SUCCEEDED(hr))
{
cout << "Successfully authenticated";
}
else
cout << "Incorrect username or password";
return hr;
}
Depending on your setup, you might have to tweak ADS_AUTHENTICATION_ENUM. I suggest you install SSL Certificate and use ADS_USE_SSL binding. Dealing with passwords without SSL in AD, can be nightmare.
Related
I've done a lot of looking around but I can't seem to find a decent solution to this problem. Many of the StackOverflow posts are regarding Ruby, but I'm using OpenSSL more or less directly (via the https://gitlab.com/eidheim/Simple-Web-Server library) for a C++ application/set of libraries, and need to work out how to fix this completely transparently for users (they should not need to hook up any custom certificate verification file in order to use the application).
On Windows, when I attempt to use the SimpleWeb HTTPS client, connections fail if I have certificate verification switched on, because the certificate for the connection fails to validate. This is not the case on Linux, where verification works fine.
I was advised to follow this solution to import the Windows root certificates into OpenSSL so that they could be used by the verification routines. However, this doesn't seem to make any difference as far as I can see. I have dug into the guts of the libssl verification functions to try and understand exactly what's going on, and although the above answer recommends adding the Windows root certificates to a new X509_STORE, it appears that the SSL connection context has its own store which is set up when the connection is initialised. This makes me think that simply creating a new X509_STORE and adding certificates there is not helping because the connection doesn't actually use that store.
It may well be that I've spent so much time debugging the minutiae of libssl that I'm missing what the actual approach to solving this problem should be. Does OpenSSL provide a canonical way of looking up system certificates that I'm not setting? Alternatively, could the issue be the way that the SimpleWeb library/ASIO is initialising OpenSSL? I know that the library allows you to provide a path for a "verify file" for certificates, but I feel like this wouldn't be an appropriate solution since I as a developer should be using the certificates found on the end user's system, rather than hard-coding my own.
EDIT: For context, this is the code I'm using in a tiny example application:
#define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
static void LoadSystemCertificates()
{
HCERTSTORE hStore;
PCCERT_CONTEXT pContext = nullptr;
X509 *x509 = nullptr;
X509_STORE *store = X509_STORE_new();
hStore = CertOpenSystemStore(NULL, "ROOT");
if (!hStore)
{
return;
}
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != nullptr)
{
const unsigned char* encodedCert = reinterpret_cast<const unsigned char*>(pContext->pbCertEncoded);
x509 = d2i_X509(nullptr, &encodedCert, pContext->cbCertEncoded);
if (x509)
{
X509_STORE_add_cert(store, x509);
X509_free(x509);
}
}
CertCloseStore(hStore, 0);
}
static void MakeRequest(const std::string& address)
{
using Client = SimpleWeb::Client<SimpleWeb::HTTPS>;
Client httpsClient(address);
httpsClient.io_service = std::make_shared<asio::io_service>();
std::cout << "Making request to: " << address << std::endl;
bool hasResponse = false;
httpsClient.request("GET", [address, &hasResponse](std::shared_ptr<Client::Response> response, const SimpleWeb::error_code& error)
{
hasResponse = true;
if ( error )
{
std::cerr << "Got error from " << address << ": " << error.message() << std::endl;
}
else
{
std::cout << "Got response from " << address << ":\n" << response->content.string() << std::endl;
}
});
while ( !hasResponse )
{
httpsClient.io_service->poll();
httpsClient.io_service->reset();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
int main(int, char**)
{
LoadSystemCertificates();
MakeRequest("google.co.uk");
return 0;
}
The call returns me: Got error from google.co.uk: certificate verify failed
OK, to anyone who this might help in future, this is how I solved this issue. This answer to a related question helped.
It turns out that the issue was indeed that the SSL context was not making use of the certificate store that I'd set up. Everything else was OK, bu the missing piece of the puzzle was a call to SSL_CTX_set_cert_store(), which takes the certificate store and provides it to the SSL context.
In the context of the SimpleWeb library, the easiest way to do this appeared to be to subclass the SimpleWeb::Client<SimpleWeb::HTTPS> class and add the following to the constructor:
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <wincrypt.h>
class MyClient : public SimpleWeb::Client<SimpleWeb::HTTPS>
{
public:
MyClient( /* ... */ ) :
SimpleWeb::Client<SimpleWeb::HTTPS>( /* ... */ )
{
AddWindowsRootCertificates();
}
private:
using OpenSSLContext = asio::ssl::context::native_handle_type;
void AddWindowsRootCertificates()
{
// Get the SSL context from the SimpleWeb class.
OpenSSLContext sslContext = context.native_handle();
// Get a certificate store populated with the Windows root certificates.
// If this fails for some reason, the function returns null.
X509_STORE* certStore = GetWindowsCertificateStore();
if ( sslContext && certStore )
{
// Set this store to be used for the SSL context.
SSL_CTX_set_cert_store(sslContext, certStore);
}
}
static X509_STORE* GetWindowsCertificateStore()
{
// To avoid populating the store every time, we keep a static
// pointer to the store and just initialise it the first time
// this function is called.
static X509_STORE* certificateStore = nullptr;
if ( !certificateStore )
{
// Not initialised yet, so do so now.
// Try to open the root certificate store.
HCERTSTORE rootStore = CertOpenSystemStore(0, "ROOT");
if ( rootStore )
{
// The new store is reference counted, so we can create it
// and keep the pointer around for later use.
certificateStore = X509_STORE_new();
PCCERT_CONTEXT pContext = nullptr;
while ( (pContext = CertEnumCertificatesInStore(rootStore, pContext)) != nullptr )
{
// d2i_X509() may modify the pointer, so make a local copy.
const unsigned char* content = pContext->pbCertEncoded;
// Convert the certificate to X509 format.
X509 *x509 = d2i_X509(NULL, &content, pContext->cbCertEncoded);
if ( x509 )
{
// Successful conversion, so add to the store.
X509_STORE_add_cert(certificateStore, x509);
// Release our reference.
X509_free(x509);
}
}
// Make sure to close the store.
CertCloseStore(rootStore, 0);
}
}
return certificateStore;
}
};
Obviously GetWindowsCertificateStore() would need to be abstracted out to somewhere platform-specific if your class needs to compile on multiple platforms.
I'd like to get the name of an application on Windows.
Currently I'm using EnumProcesses() to enumerate all processes and receive a list of PIDs.
Then I'm looping through all PIDs, each iteration looks like this, when aProcess[i] is the current PID:
HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, aProcesses[i]);
std::string processName = get_process_name(proc);
My get_process_name(proc) function uses GetModuleFileNameEx to get the executable path and GetProcessImageFileName in order to retrieve the name of the executable file.
What I want to retrieve is basically the App Name, as it is displayed in the Windows Task Manager.
I've looked throughout Win32 API's documentation and could not find a clue on how to achieve this.
I've tried looking for other ways such as Windows Shell tasklist but it outputs different things, for example- Google Chrome:
Image Name: chrome.exe PID: 84 Session Name: Console
I'd really appreciate any thought on the matter, whether it be the Win32 API or some other way I can implement through C++ code.
You can do this with GetFileVersionInfoA and VerQueryValueA.
You just need to follow the example given in the VerQueryValueA document.
Here is my sample:
struct LANGANDCODEPAGE {
WORD wLanguage;
WORD wCodePage;
} *lpTranslate;
int main()
{
HANDLE handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION , FALSE, 2140); //Modify pid to the pid of your application
if (!handle) return 0;
wchar_t pszFile[MAX_PATH] = L"";
DWORD len = MAX_PATH;
QueryFullProcessImageName(handle, 0, pszFile, &len);
UINT dwBytes, cbTranslate;
DWORD dwSize = GetFileVersionInfoSize(pszFile, (DWORD*)&dwBytes);
if (dwSize == 0) return 0;
LPVOID lpData = (LPVOID)malloc(dwSize);
ZeroMemory(lpData, dwSize);
if (GetFileVersionInfo(pszFile, 0, dwSize, lpData))
{
VerQueryValue(lpData,
L"\\VarFileInfo\\Translation",
(LPVOID*)&lpTranslate,
&cbTranslate);
wchar_t strSubBlock[MAX_PATH] = { 0 };
wchar_t* lpBuffer;
for (int i = 0; i < (cbTranslate / sizeof(struct LANGANDCODEPAGE)); i++)
{
StringCchPrintf(strSubBlock,50,
L"\\StringFileInfo\\%04x%04x\\FileDescription",
lpTranslate[i].wLanguage,
lpTranslate[i].wCodePage);
VerQueryValue(lpData,
strSubBlock,
(void**)&lpBuffer,
&dwBytes);
std::wcout << lpBuffer << std::endl;
}
}
if(lpData) free(lpData);
if (handle) CloseHandle(handle);
return 0;
}
And it works for me:
I think what you want are the "version" resources embedded in the PE file (the executables.)
You seem to be familiar with using Win32 API, so I'm just going to give you some hints.
You have to use LoadLibraryEx to load the EXE file (the Ex suffix is to enable passing the LOAD_LIBRARY_AS_DATAFILE flag,) and then call EnumResourceTypes (also see EnumResourceNames) to enumerate all the resource types/resources in the file, and find what you are looking for and then extract the data with LoadResource. The resource type you want is RT_VERSION.
I'm sure I'm omitting a lot of details (as per usual for Win32 programming,) and there might not be a need for enumeration at all; in which case you may want to call FindResource or FindResourceEx directly (if there is a fixed name for this particular resource.)
As further clarification, this gives you the date you see if you right-click on the EXE file (not the shortcut) in Windows Explorer and select "Properties", then go to the "Details" tab. If that information is indeed what you want (e.g. the "File description" field) then the above method should give you the data.
I've tried to implement a program which controls if the name of the user logged into the computer and the user who ran a program is the same, but I think I've done something wrong. The name of the usernamed logged works, but not the one to get the user who ran the program.
#include <Windows.h>
#include <iostream>
#include <stdlib.h>
#include <Lmcons.h>
#include <tchar.h>
#include <stdio.h>
#define INFO_BUFFER_SIZE 32767
using namespace std;
void Test() {
WCHAR UsernameSSR[INFO_BUFFER_SIZE];
TCHAR UsernameWindows[INFO_BUFFER_SIZE];
DWORD bufCharCountWindows = INFO_BUFFER_SIZE;
DWORD bufCharCountSSR = INFO_BUFFER_SIZE;
GetUserName(UsernameWindows, &bufCharCountWindows);
GetUserNameW(UsernameSSR, &bufCharCountSSR);
cout << UsernameWindows << " and " << UsernameSSR;
return;
}
GetUserName() is just a preprocessor macro that maps to either GetUserNameA() (ANSI) or GetUserNameW() (Unicode), depending on your project setup. Either way, GetUserName(A|W) returns the username currently associated with the calling thread, which in your example is also the username that is used to run the program. No mixing of GetUserNameA() and GetUserNameW() together will get you the results you want, because they are going to return the same username just in different character encodings.
To get the username that is logged into Windows itself, you need a different function, such as WTSQuerySessionInformation() (A or W variant), eg:
#include <iostream>
#include <Windows.h>
#include <wtsapi32.h>
#include <Lmcons.h>
void Test()
{
WCHAR UsernameSSR[UNLEN+1];
DWORD bufCharCountSSR = UNLEN+1;
if (GetUserNameW(UsernameSSR, &bufCharCountSSR))
std::wcout << L"UsernameSSR: " << UsernameSSR << std::endl;
else
std::wcout << L"Error getting UserNameSSR" << std::endl;
// alternatively, use GetUserNameExW() instead...
LPWSTR UsernameWindows;
DWORD bufByteCountWindows;
if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSUserName, &UsernameWindows, &bufByteCountWindows))
{
LPWSTR Domain;
DWORD bufByteCountDomain;
std::wcout << L"UsernameWindows: ";
if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSDomainName, &Domain, &bufByteCountDomain))
{
if (*Domain)
std::wcout << Domain << L"\\";
WTSFreeMemory(Domain);
}
std::wcout << UsernameWindows << std::endl;
WTSFreeMemory(UsernameWindows);
}
else
std::wcout << L"Error getting UserNameWindows" << std::endl;
// alternatively, query WTSQuerySessionInformation() for WTSSessionInfo, which
// returns both Domain and UserName in a single call, amongst other pieces of info...
}
That being said, comparing username strings is not the best way to determine whether your program is being run by the same user that is logged into Windows.
For instance, another way would be to obtain the Security Identifier (SID) of the user who is running the program and then compare it to the SID of the user that is logged into Windows using EqualSid().
Getting the SID of the calling process is easy: you can use GetCurrentProcessId(), OpenProcessToken(TOKEN_QUERY), and GetTokenInformation(TokenUser).
However, getting the SID of the logged in Windows session is a bit trickier. You can either:
get the Session ID of the logged in user, such as with ProcessIdToSessionId() or QueryTokenInformation(TokenSessionId) for the parent process that spawned your program (to find the parent process ID, use CreateToolhelp32Snapshot(), Process32First(), and Process32Next()), and then pass that Session ID to WTSQueryUserToken(), and then query the SID from that token. The gotcha is that WTSQueryUserToken() can only be called from a service that is running under the LocalSystem account, so you will have to write such a service and delegate to it via an IPC mechanism of you choosing.
retrieve the domain\username of the user session as described further above, and then use WMI to query the Win32_UserAccount table for that specific user and read its Sid property, then parse the SID into its binary form using ConvertStringSidToSid().
You missed , after first parameter (name):
GetUserNameA((LPSTR)name(LPDWORD) & size)
Also, why did you use GetUserName and not GetUserNameW? In some cases GetUserName will be same as GetUserNameA, so if you try to get the difference write GetUserNameW directly.
I'm trying to write a function to get the Windows-equivalent of HOME. My C skills are rusty, so don't mind that my example code does not compile. I'm trying to use SHGetKnownFolderPath on Windows Vista and newer, and SHGetFolderPath on Server 2003 and older. Since I expect to encounter users running Windows XP (as it is still the number one deployed version of Windows), I am going to avoid having a reference to SHGetKnownFolderPath in the symbol table (as this would lead to a binary that won't even load on XP). I know to LoadLibrary() shell32 and to GetProcAddress() from there, but my skills on doing function pointers are, well, crap, to say the least.
When I write features, and they are difficult to handle, I isolate them into an separate example file. The broken example I have so far is:
#include <windows.h>
#include <stdio.h>
// Pointerizing this Vista-and-later call for XP/2000 compat, etc.
typedef HRESULT (WINAPI* lpSHGetKnownFolderPath)(
REFKNOWNFOLDERID rfid,
DWORD dwFlags,
HANDLE hToken,
PWSTR *ppszPath
) lpSHGetKnownFolderPath;
int main(int argc, char *argv[])
{
// SHGet(Known)FolderPath() method.
HMODULE hndl_shell32;
lpSHGetKnownFolderPath pSHGetKnownFolderPath;
hndl_shell32 = LoadLibrary("shell32");
pSHGetKnownFolderPath = GetProcAddress(hndl_shell32, "SHGetKnownFolderPathW");
if(pSHGetKnownFolderPath != NULL) {
} else {
}
}
My question is this: Knowing that I'm doing this wrong, how would I go about doing this right? And an explanation as to how to do it right in the future would be appreciated. Thanks.
Here is a small application that shows how to use LoadLibrary() and GetProcAddress() with advice provided in comments:
#include <windows.h>
#include <stdio.h>
#include <shlobj.h>
/* The name of the function pointer type is
'lpSHGetKnownFolderPath', no need for
additional token after ')'. */
typedef HRESULT (WINAPI* lpSHGetKnownFolderPath)(
REFKNOWNFOLDERID rfid,
DWORD dwFlags,
HANDLE hToken,
PWSTR *ppszPath
);
int main()
{
HMODULE hndl_shell32;
lpSHGetKnownFolderPath pSHGetKnownFolderPath;
/* Always check the return value of LoadLibrary. */
hndl_shell32 = LoadLibrary("shell32");
if (NULL != hndl_shell32)
{
/* There is no 'SHGetKnownFolderPathW()'.
You need to cast return value of 'GetProcAddress()'. */
pSHGetKnownFolderPath = (lpSHGetKnownFolderPath)
GetProcAddress(hndl_shell32, "SHGetKnownFolderPath");
if(pSHGetKnownFolderPath != NULL)
{
PWSTR user_dir = 0;
if (SUCCEEDED(pSHGetKnownFolderPath(
FOLDERID_Profile,
0,
NULL,
&user_dir)))
{
/* Use 'user_dir' - remember to:
CoTaskMemFree(user_dir);
when no longer required.
*/
}
}
else
{
fprintf(stderr, "Failed to locate function: %d\n",
GetLastError());
}
/* Always match LoadLibrary with FreeLibrary.
If FreeLibrary() results in the shell32.dll
being unloaded 'pSHGetKnownFolderPath' is
no longer valid.
*/
FreeLibrary(hndl_shell32);
}
else
{
fprintf(stderr, "Failed to load shell32.dll: %d\n", GetLastError());
}
return 0;
}
This was compiled on Windows XP.
Output on Windows XP:
Failed to locate function: 127
where 127 means The specified procedure could not be found.
Output on Windows Vista:
C:\Users\admin
You can always use getenv("HOMEDRIVE") and getenv("HOMEPATH") and concatenate the results.
std::string home = std::string(getenv("HOMEDRIVE")) + getenv("HOMEPATH");
The Windows equivalent of HOME is USERPROFILE. It is an ordinary environment variable just like in Linux. You can make the following call to retrieve it:
char *profilepath = getenv("USERPROFILE");
I am working on getting the version of the Software which is installed on the Computer. I have implemented the logic for reading the Uninstall hive of registry, but i have observed that some of the software are not having version entries in the Uninstall hive of the registry. But i want to show the version of those softwares also.
Can some one help me out in this regard?
Supplying a software version to the registry of Windows is voluntary. If the developer of the software you're looking at chose to not display the version there or was simply unaware of such possibility, I am unable to point you to any other location he would choose to use or be aware of. In fact, the software might not even have a version number/name.
Ask yourself this: Where else is the Version detail of the software available if not in the registry? If it is available somewhere else other than registry, ask us if you could get that detail using C++. I guess this would be a better approach to solve your issue.
Added the information below since OP is looking for file version
See if the below code could help you.
CString GetFileVersionInfo(CString strFile, CString strProperty)
{
int rc;
UINT nLen;
DWORD nSize;
DWORD dwHandle = 0;
CString strBuffer;
CString strValue;
CString strBlock;
void *lpPropertyBuffer;
struct LANGANDCODEPAGE
{
WORD wLanguage;
WORD wCodePage;
} *lpTranslate;
nSize = GetFileVersionInfoSize(strFile.GetBuffer(strFile.GetLength()), &dwHandle);
::GetFileVersionInfo(strFile.GetBuffer(strFile.GetLength()), 0, nSize, strBuffer.GetBuffer(nSize));
// Read the list of languages and code pages.
if (VerQueryValue(strBuffer.GetBuffer(strBuffer.GetLength()), "\\VarFileInfo\\Translation", (LPVOID *) &lpTranslate, &nLen))
{
strBlock.Format("\\StringFileInfo\\%04x%04x\\%s",
lpTranslate->wLanguage,
lpTranslate->wCodePage,
strProperty);
rc = VerQueryValue(strBuffer.GetBuffer(strBuffer.GetLength()), strBlock.GetBuffer(nSize), &lpPropertyBuffer, &nLen);
if (rc != 0 && nLen > 0)
{
strncpy(strValue.GetBuffer(nLen + 1), (char *) lpPropertyBuffer, nLen);
strValue.ReleaseBuffer(nLen);
}
}
return strValue;
}
user version.lib while linking and you might need winver.h for compilation. You can call the function like this
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
AfxMessageBox(GetFileVersionInfo("shell32.dll", "ProductVersion"));
}
return nRetCode;
}
I'd say look at the file version information. And you might find this article useful on how the Add/Remove Programs dialog gets its information.
If the software developers chose not to add version information into Uninstall information, then there's no reliable way to get it.
You can try to find where application is installed. But even if you have the path, the application can consist of several .exe files which can have different versions and product names. If you add DLLs into the candidate list for getting version information, your results become even less predictable.