I have a C++ application in Visual Studio 2010 and I have a Windows Installer (i.e. setup project) to install it. I want to be able to invoke the installer like this:
Setup1.msi MYPROPERTY=MyValue
And then be able to extract the value "MyValue" from the property from within my custom action.
I tried to get it working by following this tutorial(C++ custom actions) and this tutorial (passing arguments to custom actions, but in C#) combined with some MSDN searches to get this code:
#define WINDOWS_LEAN_AND_MEAN
#include <Windows.h>
#include <msi.h>
#include <msiquery.h>
#include <stdio.h>
BOOL APIENTRY DllMain(HANDLE, DWORD, LPVOID) {
return TRUE;
}
UINT APIENTRY InstallCustomAction(MSIHANDLE install_handle) {
static const wchar_t* kPropertyName = L"MYPROPERTY";
//auto msi_handle = MsiGetActiveDatabase(install_handle);
DWORD n = 0;
//auto result = MsiGetProperty(msi_handle, kPropertyName, L"", &n);
auto result = MsiGetProperty(install_handle, kPropertyName, L"", &n);
wchar_t* value = nullptr;
if (result == ERROR_MORE_DATA) {
++n;
value = new wchar_t[n];
//result = MsiGetProperty(msi_handle, kPropertyName, value, &n);
result = MsiGetProperty(install_handle, kPropertyName, value, &n);
}
if (result == ERROR_SUCCESS) {
wchar_t buffer[128];
swprintf_s(buffer, L"n = %d, value = %s", n, value);
MessageBox(nullptr, buffer, L"CustomAction", MB_OK);
} else {
MessageBox(nullptr, L"Error reading property", L"Error", MB_OK);
}
delete value;
//MsiCloseHandle(msi_handle);
return ERROR_SUCCESS;
}
I'm following the C# tutorial exactly in terms of the IDE (I have Entry Point set to InstallCustomAction and Custom Action Data set to /MYPROPERTY=[MYPROPERTY]) The custom action fires correctly but I don't get the parameter.
With the code as-is, I get n=0. If I use the msi_handle from MsiGetActiveDatabase I get an error (i.e. MsiGetProperty returns something other than ErrorSuccess).
How can I get the property that the user passes in on the command line from within my custom action?
There's no need to call MsiGetActiveDatabase. From what you've written it sounds like your custom action is scheduled for deferred execution. In that scenario you should be trying to get the property named "CustomActionData".
Related
I have a DLL which is acting like a hooking engine. I am going to inject this library to processes in order to analysis APIs that they are calling.
It will hook functions like MessageBoxA after loading to address space of the target process. The following method parse IAT table and then try to overwrite MessageBoxA address in IAT table with modified API (my own api).
void triggered()
{
PIMAGE_IMPORT_DESCRIPTOR import_descriptor = import_directory_table();
while (import_descriptor->Name != NULL)
{
m_lib_name = reinterpret_cast<LPCSTR>(import_descriptor->Name + (DWORD_PTR)va_image_base());
m_library = LoadLibraryA(m_lib_name);
if (m_library)
{
m_original_first_thunk = reinterpret_cast<PIMAGE_THUNK_DATA>((DWORD_PTR)va_image_base() + import_descriptor->OriginalFirstThunk);
m_first_thunk = reinterpret_cast<PIMAGE_THUNK_DATA>((DWORD_PTR)va_image_base() + import_descriptor->FirstThunk);
while (m_original_first_thunk->u1.AddressOfData != NULL)
{
m_function_name = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>((DWORD_PTR)va_image_base() + pe::m_original_first_thunk->u1.AddressOfData);
// MessageBoxA redirected to _MessageBox which is a modified version of Sleep API
if (std::string(m_function_name->Name).compare("MessageBoxA") == 0)
{
VirtualProtect((LPVOID)(&m_first_thunk->u1.Function), 8, PAGE_READWRITE, &m_old_protect);
m_first_thunk->u1.Function = (DWORD_PTR)hook::statically::ui::_MessageBoxA;
}
++m_original_first_thunk;
++m_first_thunk;
}
}
import_descriptor++;
}
}
But when Dll loaded into the process, target process when call MessageBoxA, it give me access violation in debugging mode of visual studio. In the following, you will see the code _MessageBoxA.
namespace hook
{
namespace statically
{
namespace ui
{
// MessageBox call redirected to this function.
int _MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
if (std::string(lpText).compare("test") == 0)
{
return load_time::prototype::ui::OMessageBoxA(NULL, "Hahahahahhaa", "Hahahahaha", MB_OK);
}
else
{
return load_time::prototype::ui::OMessageBoxA(hWnd, lpText, lpCaption, uType);
}
return 0;
}
}
}
}
Where is my mistake? I did research a lot in google but couldn't find out a solution.
I have an installer that tries to (re)start my application in the current user context after the installation is done.
The installer runs in the SYSTEM context and before launching the application it attempts (and theoretically succeeds) to impersonate the current user. However, when I look in the task manager, I see that my application is running in the SYSTEM context.
This is (a snippet from) my code:
TCHAR szUsername[128] = _T("");
DWORD dwUsernameSize = 128;
GetUserName(szUsername, &dwUsernameSize);
// Lets the calling process impersonate the security context of a logged-on user.
if (!ImpersonateLoggedOnUser(hToken))
{
throw Win32Exception(GetLastError(), _T("Failed to impersonate current user"));
}
TCHAR szUsername2[128] = _T("");
DWORD dwUsernameSize2 = 128;
GetUserName(szUsername2, &dwUsernameSize2);
MLOGD(_T("ProcessUtils::StartProcessInCurrentUserContext: Successfully impersonated %s"), szUsername2);
ProcessUtils::StartProcess(sExeName, lstParams, sWorkingDir, bWaitToFinish, errCode);
ProcessUtils::StartProcess is a wrapper around CreateProcess.
szUsername contains SYSTEM and szUsername2 contains the current user. So ImpersonateLoggedOnUser is successful.
However, as mentioned above, the process is started in the SYSTEM context, not the current user one.
I'm not sure how helpful this might be, but my installer is written in NSIS and it's calling the function that contains the code from above via a plugin written in C/C++.
Does anyone know why my application doesn't start in the current user context?
Win32 CreateProcess creates a process in the same security context as the caller which is SYSTEM (even though you are impersonating).
Think you need to be calling CreateProcessAsUser.
I had a very similar problem a couple of years ago when I was also
working on an installer application. After A LOT of frustration, caused
by failed attempts to start an application in the context of the current
user using CreateProcessAsUser, I've finally given up. After a thorough
search on the web, I've found a briliant implementation that uses
IShellDispatch2 interface. Here is an example:
#include <Windows.h>
#include <exdisp.h>
#include <Shobjidl.h>
#include <Shlwapi.h>
#include <comutil.h>
#include <SHLGUID.h>
#include <cstdlib>
#include <iostream>
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "comsuppw.lib")
bool ShellExecuteAsCurrentUser(const TCHAR *pcOperation, const TCHAR *pcFileName, const TCHAR *pcParameters,
const TCHAR *pcsDirectory, const DWORD dwShow)
{
bool bSuccess = false;
IShellWindows *psw = NULL;
HRESULT hr = CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw));
if(SUCCEEDED(hr))
{
HWND hwnd = 0;
IDispatch* pdisp = NULL;
_variant_t vEmpty;
if(S_OK == psw->FindWindowSW(&vEmpty, &vEmpty, SWC_DESKTOP, reinterpret_cast<long*>(&hwnd), SWFO_NEEDDISPATCH, &pdisp))
{
if((hwnd != NULL) && (hwnd != INVALID_HANDLE_VALUE))
{
IShellBrowser *psb;
hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb));
if(SUCCEEDED(hr))
{
IShellView *psv = NULL;
hr = psb->QueryActiveShellView(&psv);
if(SUCCEEDED(hr))
{
IDispatch *pdispBackground = NULL;
HRESULT hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground));
if(SUCCEEDED(hr))
{
IShellFolderViewDual *psfvd = NULL;
hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd));
if(SUCCEEDED(hr))
{
IDispatch *pdisp = NULL;
hr = psfvd->get_Application(&pdisp);
if(SUCCEEDED(hr))
{
IShellDispatch2 *psd;
hr = pdisp->QueryInterface(IID_PPV_ARGS(&psd));
if(SUCCEEDED(hr))
{
_variant_t verb(pcOperation);
_variant_t file(pcFileName);
_variant_t para(pcParameters);
_variant_t dir(pcsDirectory);
_variant_t show(dwShow);
if(SUCCEEDED(psd->ShellExecute(file.bstrVal, para, vEmpty, verb, show)))
bSuccess = true;
psd->Release();
psd = NULL;
}
pdisp->Release();
pdisp = NULL;
}
}
pdispBackground->Release();
pdispBackground = NULL;
}
psv->Release();
psv = NULL;
}
psb->Release();
psb = NULL;
}
}
pdisp->Release();
pdisp = NULL;
}
psw->Release();
psw = NULL;
}
return bSuccess;
}
int main(int argc, char *argv[])
{
CoInitialize(NULL);
if(ShellExecuteAsCurrentUser(L"open", L"notepad", nullptr, nullptr, SW_SHOWNORMAL))
std::cout << "SUCCESS" << std::endl;
CoUninitialize();
return 0;
}
This is just a quick demo, the implementation of ShellExecuteAsCurrentUser can be
improved by using smart pointers for COM interfaces and some refactoring. This method
worked for me on versions WinXP SP3 - Win 8.1, not sure if it works on Windows 10. For
more details, check the authors github page:
https://github.com/lordmulder/stdutils/tree/master/Contrib/StdUtils
If you had read the documentation for CreateProcess, you would have found the answer to your question in the first three sentences:
Creates a new process and its primary thread. The new process runs in the security context of the calling process.
If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token.
There really isn't much else to say; the behaviour you describe is as documented. If you want to create a process as another user, you must use CreateProcessAsUser or one of the related functions.
I am working on an installer project in Advanced Installer 10.2. I found out that I can use a DLL for serial validation then I found this resource on their website.
I succeeded in building that DLL, here is my code:
// SerialValidationLib.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "SerialValidationLib.h"
#include <Msi.h>
#include <MsiQuery.h>
#include <MsiDefs.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// The one and only application object
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(NULL);
if (hModule != NULL)
{
// initialize MFC and print and error on failure
if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: MFC initialization failed\n"));
nRetCode = 1;
}
else
{
// TODO: code your application's behavior here.
}
}
else
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: GetModuleHandle failed\n"));
nRetCode = 1;
}
return nRetCode;
}
UINT __stdcall ValidateSerial_Sample(MSIHANDLE hInstall)
{
TCHAR szPidKey[256];
DWORD dwLen = sizeof(szPidKey)/sizeof(szPidKey[0]);
//retrive the text entered by the user
UINT res = MsiGetProperty(hInstall, _T("PIDKEY"), szPidKey, &dwLen);
if(res != ERROR_SUCCESS)
{
//fail the installation
return 1;
}
bool snIsValid = false;
//validate the text from szPidKey according to your algorithm
//put the result in snIsValid
TCHAR * serialValid;
if(snIsValid)
serialValid = _T("TRUE");
else
{
//eventually say something to the user
MessageBox(0, _T("Serial invalid!"), _T("Message"), MB_ICONSTOP);
serialValid = _T("FALSE");
}
res = MsiSetProperty(hInstall, _T("SERIAL_VALIDATION"), serialValid);
if(res != ERROR_SUCCESS)
{
return 1;
}
//the validation succeeded - even the serial is wrong
//if the SERIAL_VALIDATION was set to FALSE the installation
//will not continue
return 0;
}
I also imported it to Advanced Installer, look here:
But when I run the installer, and try to proceed with the installation, after serial insertion point, I get this error message:
Where is my mistake? Does anybody know a good tutorial about this? I searched on the internet, but nothing helps me...
You could have two problems:
either you have typed the method name instead of picking it from the combo loaded by Advanced Installer. In this case the installer fails to call the method from the DLL, as it cannot find it.
or, there is a problem with your code, in which case you need to debug it, as you would do with a normal custom action, attaching from VS (add a mesagebox with a breakpoint after it).
I've got a problem about RegOpenKeyEx, the code:
#include <tchar.h>
#include <stdio.h>
#include <windows.h>
#pragma comment (lib, "Advapi32.lib")
int main () {
TCHAR *keyName = _T("SOFTWARE\\foobar2000\\capabilities");
HKEY key = NULL;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS) {
printf("open key failed!\n");
return -1;
} else {
printf("open key success!\n");
}
TCHAR *value = _T("123");
if (RegSetValueEx(key, _T("xxx"), 0, REG_SZ,
(const BYTE *)value, sizeof(TCHAR) * (_tcslen(value) + 1)) != ERROR_SUCCESS) {
printf("set value failed!\n");
}
RegCloseKey(key);
return 0;
}
Save the code in such as reg.cpp, and in command mode:
cl reg.cpp
and I got reg.exe, run it:
D:\tmp>reg.exe
open key success!
But the value hasn't been written in the registry.
Another strange thing is that if I use the visual studio to create a CLI project, and paste the code into main(), the RegOpenKeyEx() will return false.
The platform is windows 7, and UAC is enabled.
Sounds like you're running into virtualization. IF the app has no manifest, when you try to write to HKLM\Software it actually writes to HKEY_USERS\<User SID>_Classes\VirtualStore\Machine\Software. To prevent this, you can run the app elevated. You might want to add a manifest forcing it to run elevated every time. Alternatively, stop writing to HKLM and use HKCU instead.
As for the C++/CLI part, my guess would be you are given an asInvoker manifest for that one, which suppresses virtualization and results in the attempt to get to HKLM failing.
I am trying to display file type of given filename (based on extension) using AssocQueryKey() API function.
The problem is thar return wrong HKEY value sometimes. For example the following function works correctly on win 7 ultimate x64, but fails for some extensions like ".mp3" on my win xp x86 machine (other extensions works though).
Even when "succeeded" and returns S_OK, GetLastError() is 1008 ALWAYS after AssocQueryKey() call:
// Return STL string representation of file type from windows registry
stlstring GetFileTypeFromRegistry(const stlstring& m_filename)
{
CRegKey reg;
HKEY key = {0};
stlstring s;
//Get file extension
LPCTSTR fExt = PathFindExtension(m_filename.c_str());
if(AssocQueryKey(NULL, ASSOCKEY_CLASS, fExt, TEXT(""), &key) != S_OK)
DisplayError(_T("AssocQueryKey != S_OK"), GetLastError());
else
DisplayError(_T("AssocQueryKey == S_OK"), GetLastError());
if(reg.Open ( key, NULL, KEY_QUERY_VALUE) != ERROR_SUCCESS){
reg.Close();
DisplayError((LPTSTR)fExt);
return s;
}
//DWORD out = 0;
/*WCHAR *h = new WCHAR[1024];
ZeroMemory(h, sizeof(h));
AssocQueryStringByKey(0, ASSOCSTR_EXECUTABLE, HKEY_CLASSES_ROOT, NULL, h, &out);
//MessageBox(0,_T("gbtbb"),h,MB_OK);
delete[] h;*/
ULONG m_sz = 256;
//if( reg.QueryStringValue(NULL, NULL, &m_sz) == ERROR_SUCCESS){
TCHAR *m_regstring = new TCHAR[m_sz + 1];
if(reg.QueryStringValue(NULL, m_regstring, &m_sz) == ERROR_SUCCESS){
//DisplayError(_T(""));
s += m_regstring;
/*delete[] m_regstring; m_regstring = NULL;
reg.Close();
return s;*/
} else {
DisplayError(_T("CRegKey::QueryStringValue()"), GetLastError());
}
s += m_regstring;
delete[] m_regstring; m_regstring = NULL;
reg.Close();
return s;
/*}
reg.Close();
return s;*/
}
Any ideas on this ?? This function is from a DLL which is loaded by windows explorer, implementing IQueryInfo::GetInfoTip() if that matters.
You shouldn't use GetLastError for functions that return the error code directly. The MSDN page for AssocQueryKey says "Returns S_OK if successful, or a COM error value otherwise.", which means you already get the error code in the return value.
If you just want to get the file type information, there's a much simpler solution: SHGetFileInfo. It's really simple to use, like this:
SHFILEINFO shfi;
SHGetFileInfo(filename, 0, &shfi, sizeof(shfi), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES);
// shfi.szTypeName now contains the file type string of the given filename