Does Marshal.PtrToStringAnsi not working in windows service? - c#

I have written a dll which call windows spooler api to get printer name and printer driver name, it works fine in console application. However, I got nothing when I call the dll in windows services application.
Here is my dll source code(i.e. printerUtility):
PRINTER_ENUM_LOCAL = 0x00000002,
PRINTER_ENUM_CONNECTIONS = 0x00000004,
public ArrayList getAllLocalPrinterInfo(LogWriter logger)
{
ArrayList printerInfoList = getAllLocalPrinterInfoV2(PrinterEnumFlags.PRINTER_ENUM_LOCAL | PrinterEnumFlags.PRINTER_ENUM_CONNECTIONS);
foreach (PrinterInfoV2 printerInfo in printerInfoList)
{
logger.write("In dll Printer Name pointer:" + printerInfo.pPrinterName+"\nIn dll Printer Name:" + Marshal.PtrToStringAnsi(printerInfo.pPrinterName));
logger.write("In dll Driver Name pointer:" + printerInfo.pDriverName+"\nIn dll Driver Name:" + Marshal.PtrToStringAnsi(printerInfo.pDriverName));
}
//return allLocalPrinterInfo;
return printerInfoList;
}
public ArrayList getAllLocalPrinterInfoV2(PrinterEnumFlags Flags)
{
return enumPrinters(Flags, 2);
}
private ArrayList enumPrinters(PrinterEnumFlags Flags, UInt32 level)
{
bool result;
ArrayList printerInfo = new ArrayList();
UInt32 returned, needed = 0;
UInt32 flags = Convert.ToUInt32(Flags);
//find required size for the buffer
result = EnumPrinters(flags, null, level, IntPtr.Zero, 0, out needed, out returned);
if (Marshal.GetLastWin32Error() != Convert.ToUInt32(CredUIReturnCodes.ERROR_INSUFFICIENT_BUFFER))
{
throw new Exception("EnumPrinters 1 failure, error code=" + Marshal.GetLastWin32Error());
}
else
{
IntPtr buffer = Marshal.AllocHGlobal((int)needed);
result = EnumPrinters(flags, null, level, buffer, needed, out needed, out returned);
if (result)
{
if ((level > 0) || (level < 5))
{
Type type = typeof(PrinterInfoV1);
switch (level)
{
case 1:
type = typeof(PrinterInfoV1);
break;
case 2:
type = typeof(PrinterInfoV2);
break;
case 3:
type = typeof(PrinterInfoV3);
break;
case 4:
type = typeof(PrinterInfoV4);
break;
}
int offset = buffer.ToInt32();
int increment = Marshal.SizeOf(type);
for (int i = 0; i < returned; i++)
{
printerInfo.Add(Marshal.PtrToStructure(new IntPtr(offset), type));
offset += increment;
}
Marshal.FreeHGlobal(buffer);
return printerInfo;
}
else
{
Marshal.FreeHGlobal(buffer);
throw new Exception("The value of level is out of range");
}
}
else
{
Marshal.FreeHGlobal(buffer);
throw new Exception("EnumPrinters 2 failure, error code=" + Marshal.GetLastWin32Error());
}
}
}
[DllImport("winspool.drv", SetLastError = true)]
static extern bool EnumPrinters([InAttribute()] UInt32 Flags,
[InAttribute()] string pPrinterName,
[InAttribute()] UInt32 Level,
[OutAttribute()] IntPtr pPrinter,
[InAttribute()] UInt32 cbBuf,
[OutAttribute()] out UInt32 pcbNeeded,
[OutAttribute()] out UInt32 pcReturned);
The LogWriter Source code:
public class LogWriter
{
EventLog eventLog = null;
public LogWriter(System.Diagnostics.EventLog elog)
{
this.eventLog = elog;
}
public void write(String msg)
{
if (eventLog != null)
{
eventLog.WriteEntry(msg);
}
}
}
Here is services source code:
protected override void OnStart(string[] args)
{
serviceStatus.dwWaitHint = 100000;
serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
SetServiceStatus(this.ServiceHandle, ref serviceStatus);
printerMonitorList = new ArrayList();
try
{
ArrayList printerInfoList = printerUtility.getAllLocalPrinterInfo(logger);
foreach (PrinterInfoV2 printerInfo in printerInfoList)
{
logger.write("In service Printer Name pointer:" + printerInfo.pPrinterName+"\nIn service Printer Name:" + Marshal.PtrToStringAnsi(printerInfo.pPrinterName));
logger.write("In service Driver Name pointer:" + printerInfo.pDriverName+"\nIn service Driver Name:" + Marshal.PtrToStringAnsi(printerInfo.pDriverName));
}
serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
SetServiceStatus(this.ServiceHandle, ref serviceStatus);
logger.write(this.ServiceName + " Has Been Started");
}
catch (Exception err)
{
logger.write("Print Alert Service cannot be started:"+err.Message);
}
In event viewer, I got an empty string for both printer name and printer driver name.
However, I found that the dll return a pointer for both printer name and printer driver name. Therefore, I suspect whether the Marshal.PtrToStringAnsi function not working in window services environment.

Related

CreateProcessAsUser process exits with -1073741502

I have a service that is responsible for starting/monitoring an interactive process in user session once user logins and console session connects.
Service is set to start automatically so its up and running before user login.
All work fine on first login and i get the user process started/restarted correctly.
What happens is that if user signs out and re logins service is no longer able to start a user process correctly.
CreateProcessAsUser returns no error but as soon as user process is started its exiting with -1073741502 (0xC0000142) exit code.
If i restart the service then its again able to launch user process without any errors.
I can post full source of how the service creates user process if required.
Edit
try
{
//WE ALREADY HAVE A CLIENT ATTACHED , DONT START A NEW ONE
if (ClientProcessId != null)
return;
var ACTIVE_CONSOLE_SESSION = ListSessions()
.Where(SESSION => SESSION.State == WTS_CONNECTSTATE_CLASS.WTSActive)
.FirstOrDefault();
if (ACTIVE_CONSOLE_SESSION == null)
return;
CONSOLE_SESSION_ID = (uint)ACTIVE_CONSOLE_SESSION.Id;
IntPtr USER_TOKEN = IntPtr.Zero;
IntPtr ENVIRONMENT = IntPtr.Zero;
IntPtr LINKED_TOKEN = IntPtr.Zero;
try
{
try
{
if (!Wtsapi32.WTSQueryUserToken(CONSOLE_SESSION_ID.Value, out USER_TOKEN))
throw new Win32Exception();
}
catch (Win32Exception wex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(Wtsapi32.WTSQueryUserToken)} : console session id {CONSOLE_SESSION_ID} error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
throw;
}
try
{
if (!Userenv.CreateEnvironmentBlock(out ENVIRONMENT, USER_TOKEN, true))
throw new Win32Exception();
}
catch (Win32Exception wex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(Userenv.CreateEnvironmentBlock)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
throw;
}
try
{
LINKED_TOKEN = CoreProcess.GetLinkedTokeIfRequiered(USER_TOKEN);
}
catch (Win32Exception wex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(CoreProcess.GetLinkedTokeIfRequiered)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
throw;
}
//GET PROCESS PATHS
string FILE_NAME = EntryPoint.PROCESS_FULL_FILE_NAME;
string WORKING_DIRECTORY = EntryPoint.PROCESS_DIRECTORY;
//GET CURRENT COMMAND LINE ARGUMENTS
var CMD_ARGS = Environment.GetCommandLineArgs();
//FIRST ARGUMENT WILL ALWAYS HAVE FULL PROCESS PATH,OTHER ARGUMENTS ARE OPTIONAL
var ARGUMENTS_STRING = CMD_ARGS
.Skip(1)
.DefaultIfEmpty()
.Aggregate((first, next) => ' ' + first + ' ' + next);
var ARGUMENTS = new StringBuilder(ARGUMENTS_STRING);
var START_INFO = new STARTUPINFO();
START_INFO.cb = Marshal.SizeOf(START_INFO);
START_INFO.lpDesktop = #"winsta0\default";
var PROCESS_INFO = new PROCESS_INFORMATION();
uint dwCreationFlags = NORMAL_PRIORITY_CLASS | (int)(PROCESS_CREATE_FLAG.CREATE_NEW_CONSOLE | PROCESS_CREATE_FLAG.CREATE_UNICODE_ENVIRONMENT);
try
{
if (!AdvApi32.CreateProcessAsUser(LINKED_TOKEN,
FILE_NAME,
ARGUMENTS,
IntPtr.Zero,
IntPtr.Zero,
true,
dwCreationFlags,
ENVIRONMENT,
WORKING_DIRECTORY,
ref START_INFO,
out PROCESS_INFO))
throw new Win32Exception();
if (PROCESS_INFO.hThread != IntPtr.Zero)
{
ClientProcessId = PROCESS_INFO.dwProcessId;
ClientSessionId = CONSOLE_SESSION_ID;
EntryPoint.TryWriteToCacheLog($"{nameof(AdvApi32.CreateProcessAsUser)} : Created porocess {ClientProcessId} in session {CONSOLE_SESSION_ID}.");
}
}
catch (Win32Exception wex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(AdvApi32.CreateProcessAsUser)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
throw;
}
}
catch (Win32Exception wex)
{
switch (wex.NativeErrorCode)
{
case 5:
case 1008:
tryCount++;
if (tryCount >= START_RETRIES)
throw;
Thread.Sleep(RETRY_WAIT_SPAN);
if (DisableCallBacks)
return;
CreateProcess(tryCount);
break;
default:
throw;
}
}
catch
{
throw;
}
finally
{
Userenv.DestroyEnvironmentBlock(ENVIRONMENT);
Kernel32.CloseHandle(USER_TOKEN);
if (USER_TOKEN != LINKED_TOKEN)
Kernel32.CloseHandle(LINKED_TOKEN);
}
}
catch (Exception ex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(CreateProcess)} failed after {tryCount} retries, console seesion id {(CONSOLE_SESSION_ID != null ? CONSOLE_SESSION_ID.ToString() : "Unobtained")}.", ex.ToString());
}
finally
{
Monitor.Exit(CREATE_LOCK);
}
public static TOKEN_ELEVATION_TYPE GetTokenElevationLevel(IntPtr hToken)
{
int TOKEN_INFO_LENGTH = Marshal.SizeOf(typeof(int));
IntPtr TOKEN_INFO_POINTER = Marshal.AllocHGlobal(TOKEN_INFO_LENGTH);
try
{
if (!AdvApi32.GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevationType, TOKEN_INFO_POINTER, TOKEN_INFO_LENGTH, out TOKEN_INFO_LENGTH))
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
return (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(TOKEN_INFO_POINTER);
}
catch
{
throw;
}
finally
{
if (TOKEN_INFO_POINTER != IntPtr.Zero)
Marshal.FreeHGlobal(TOKEN_INFO_POINTER);
}
}
public static IntPtr GetLinkedTokeIfRequiered(IntPtr hToken)
{
var TOKEN_ELEVATION = GetTokenElevationLevel(hToken);
if (TOKEN_ELEVATION != TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited)
return hToken;
int TOKEN_INFO_LENGHT = Marshal.SizeOf(typeof(IntPtr));
IntPtr LINKED_TOKEN_INFO = Marshal.AllocHGlobal(TOKEN_INFO_LENGHT);
try
{
if (!AdvApi32.GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenLinkedToken, LINKED_TOKEN_INFO, TOKEN_INFO_LENGHT, out TOKEN_INFO_LENGHT))
throw new Win32Exception();
return Marshal.ReadIntPtr(LINKED_TOKEN_INFO);
}
finally
{
if (LINKED_TOKEN_INFO != IntPtr.Zero)
Marshal.Release(LINKED_TOKEN_INFO);
}
}
Thanks you for posting the rest of the code. I see you are launching the process elevated. I added this to my test service and it still works correctly.
But I think the problem might be this line in GetLinkedTokeIfRequiered():
Marshal.Release(LINKED_TOKEN_INFO);
That should obviously be:
Marshal.FreeHGlobal(LINKED_TOKEN_INFO);
Fix that and it might just work. As things are, I'm amazed it's not crashing.
Not easy for me, digging through this. C# interop not my strong suit.
For the OP's benefit, complete source code of my test service, written in C++, which works:
#include <windows.h>
#include <wtsapi32.h>
#include <userenv.h>
#include <tchar.h>
#include <stdio.h>
#pragma comment (lib, "user32.lib")
#pragma comment (lib, "wtsapi32.lib")
#pragma comment (lib, "userenv.lib")
#pragma comment (lib, "advapi32.lib")
DWORD report_error (const char *operation)
{
DWORD err = GetLastError ();
return err;
}
// Launch notepad as currently logged-on user
DWORD LaunchProcess (DWORD SessionId, const char **failed_operation)
{
HANDLE hToken;
BOOL ok = WTSQueryUserToken (SessionId, &hToken);
if (!ok)
return report_error (*failed_operation = "WTSQueryUserToken");
void *environment = NULL;
ok = CreateEnvironmentBlock (&environment, hToken, TRUE);
if (!ok)
{
CloseHandle (hToken);
return report_error (*failed_operation = "CreateEnvironmentBlock");
}
TOKEN_LINKED_TOKEN lto;
DWORD nbytes;
ok = GetTokenInformation (hToken, TokenLinkedToken, &lto, sizeof (lto), &nbytes);
if (ok)
{
CloseHandle (hToken);
hToken = lto.LinkedToken;
}
STARTUPINFO si = { sizeof (si) } ;
PROCESS_INFORMATION pi = { } ;
si.lpDesktop = "winsta0\\default";
// Do NOT want to inherit handles here, surely
DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | /* CREATE_NEW_CONSOLE | */ CREATE_UNICODE_ENVIRONMENT;
ok = CreateProcessAsUser (hToken, "c:\\windows\\system32\\notepad.exe", NULL, NULL, NULL, FALSE,
dwCreationFlags, environment, NULL, &si, &pi);
DestroyEnvironmentBlock (environment);
CloseHandle (hToken);
if (!ok)
return report_error (*failed_operation = "CreateProcessAsUser");
CloseHandle (pi.hThread);
CloseHandle (pi.hProcess);
return 0;
}
// Determine the session ID of the currently logged-on user
DWORD GetCurrentSessionId ()
{
WTS_SESSION_INFO *pSessionInfo;
DWORD n_sessions = 0;
BOOL ok = WTSEnumerateSessions (WTS_CURRENT_SERVER, 0, 1, &pSessionInfo, &n_sessions);
if (!ok)
return 0;
DWORD SessionId = 0;
for (DWORD i = 0; i < n_sessions; ++i)
{
if (pSessionInfo [i].State == WTSActive)
{
SessionId = pSessionInfo [i].SessionId;
break;
}
}
WTSFreeMemory (pSessionInfo);
return SessionId;
}
#define SERVICE_NAME __T ("demo_service")
bool quit;
// CtrlHandler callback
DWORD WINAPI CtrlHandler (DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
if (dwControl == SERVICE_CONTROL_STOP)
quit = true;
return NO_ERROR;
}
// SvcMain callback
VOID WINAPI SvcMain (DWORD dwArgc, LPTSTR *lpszArgv)
{
// Register for callbacks
SERVICE_STATUS_HANDLE sh = RegisterServiceCtrlHandlerEx (SERVICE_NAME, CtrlHandler, NULL);
// Tell the SCM that we are up and running
SERVICE_STATUS ss = { };
ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ss.dwCurrentState = SERVICE_RUNNING;
ss.dwControlsAccepted = SERVICE_ACCEPT_STOP;
SetServiceStatus (sh, &ss);
TCHAR buf [256];
const TCHAR *title = __T ("(c) 2018 Contoso Corporation");
while (!quit)
{
DWORD response = IDOK;
DWORD SessionId = GetCurrentSessionId ();
if (SessionId == 0)
{
Sleep (2000);
continue;
}
// Pop-up a message on the screen of the currently logged-on user (session 1)
_stprintf (buf, __T ("Ready to launch..., SessionId = %d"), SessionId);
WTSSendMessage (WTS_CURRENT_SERVER_HANDLE, SessionId, (TCHAR *) title, _tcslen (title),
buf, _tcslen (buf), MB_OKCANCEL, 0, &response, TRUE);
if (response == IDCANCEL)
break;
const char *failed_operation = "";
DWORD dwResult = LaunchProcess (SessionId, &failed_operation);
// Report results
_stprintf (buf, __T ("LaunchProcess returned %lx from %s"), dwResult, failed_operation);
WTSSendMessage (WTS_CURRENT_SERVER_HANDLE, SessionId, (char *) title, _tcslen (title),
buf, _tcslen (buf), MB_OK, 0, &response, TRUE);
FILE *logfile = fopen ("g:\\temp\\service.log", "at");
if (logfile)
{
fprintf (logfile, "%s\n", buf);
fclose (logfile);
}
}
// Tell the SCM we are going away and exit
ss.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (sh, &ss);
}
// main
int main (void)
{
SERVICE_TABLE_ENTRY DispatchTable [] =
{
{ SERVICE_NAME, SvcMain },
{ NULL, NULL }
};
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
StartServiceCtrlDispatcher (DispatchTable);
return 0;
}
Error is STATUS_DLL_INIT_FAILED which means a dynamically loaded DLL is missing. Maybe you specify the wrong working directory and some call to LoadLibrary("lib_with_no_path.dll") fails?
You should be able to see which DLL is missing if you look in the Event Viewer.

c# IDsObjectPickerCredentials fail

I have to set credentials to Active-Directory-Object-Picker (IDsObjectPicker) on c#.
But I can't force the IDsObjectPickerCredentials to work.
I've made same on c++ (msdn example) and it works good.
I used "headers" from here ComInterop.cs and StructsFlags.cs
Please tell me what I'm doing wrong. How to call IDsObjectPickerCredentials.SetCredentials
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Tulpep.ActiveDirectoryObjectPicker;
namespace cred
{
class Program
{
static void Main(string[] args)
{
string szTargetComputer = #"10.0.9.115";
string szUser = #"TST\test";
string szPassword = #"123qazWSX";
DSObjectPicker picker = new DSObjectPicker();
IDsObjectPicker ipicker = (IDsObjectPicker)picker;
int res = InitObjectPicker(ipicker, szTargetComputer);
if (res == (int)HRESULT.S_OK)
{
try
{
// !!! HERE !!!
IDsObjectPickerCredentials cred = (ipicker as IDsObjectPickerCredentials);
res = cred.SetCredentials(szUser, szPassword);
// c++ like variant
// res = InitCredentials(ipicker, szUser, szPassword);
if (res != (int)HRESULT.S_OK) Console.WriteLine("SetCredentials Fail : 0x{0}", res.ToString("X4")); // On Win32 I get 0x80070057 - looks like E_INVALIDARG
IntPtr hwndParent = Process.GetCurrentProcess().MainWindowHandle;
IDataObject dataObj = null;
int hresult = ipicker.InvokeDialog(hwndParent, out dataObj);
Console.WriteLine(hresult == (int)HRESULT.S_OK ? "OK" : "Cancel");
Console.ReadKey();
}
finally
{
Marshal.ReleaseComObject(ipicker);
}
}
}
[ComImport, Guid("E2D3EC9B-D041-445A-8F16-4748DE8FB1CF"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IDsObjectPickerCredentials
{
[PreserveSig()]
int SetCredentials(
[In, MarshalAs(UnmanagedType.LPWStr)] string szUserName,
[In, MarshalAs(UnmanagedType.LPWStr)] string szPassword);
}
static int InitObjectPicker(IDsObjectPicker ipicker, string szTargetComputer)
{
int res = (int)HRESULT.S_FALSE;
DSOP_SCOPE_INIT_INFO[] aScopeInit = new DSOP_SCOPE_INIT_INFO[1];
DSOP_INIT_INFO InitInfo = new DSOP_INIT_INFO();
aScopeInit[0].cbSize = (uint)Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO));
aScopeInit[0].flType =
DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN |
DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_DOWNLEVEL_JOINED_DOMAIN;
aScopeInit[0].FilterFlags.Uplevel.flBothModes =
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_COMPUTERS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_USERS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_WELL_KNOWN_PRINCIPALS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_BUILTIN_GROUPS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_WELL_KNOWN_PRINCIPALS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_INCLUDE_ADVANCED_VIEW;
aScopeInit[0].FilterFlags.flDownlevel =
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_COMPUTERS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_USERS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_WELL_KNOWN_PRINCIPALS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_BUILTIN_GROUPS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_WELL_KNOWN_PRINCIPALS |
DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_INCLUDE_ADVANCED_VIEW;
IntPtr refScopeInitInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO)) * aScopeInit.Length);
try
{
// Marshal structs to pointers
for (int index = 0; index < aScopeInit.Length; index++)
{
Marshal.StructureToPtr(aScopeInit[index], (IntPtr)((int)refScopeInitInfo + index * Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO))), false);
}
InitInfo.cbSize = (uint)Marshal.SizeOf(typeof(DSOP_INIT_INFO));
InitInfo.cDsScopeInfos = (uint)aScopeInit.Length; //sizeof(aScopeInit)/sizeof(DSOP_SCOPE_INIT_INFO);
InitInfo.aDsScopeInfos = refScopeInitInfo;
InitInfo.flOptions = DSOP_INIT_INFO_FLAGS.DSOP_FLAG_MULTISELECT;
InitInfo.pwzTargetComputer = szTargetComputer;
res = ipicker.Initialize(ref InitInfo);
}
finally
{
Marshal.FreeHGlobal(refScopeInitInfo);
}
return res;
}
static int InitCredentials(IDsObjectPicker ipicker, string User, string Password)
{
IntPtr ptr = IntPtr.Zero;
Guid IID_IDsObjectPickerCredentials = new Guid("E2D3EC9B-D041-445A-8F16-4748DE8FB1CF");
IntPtr pIUnk = Marshal.GetIUnknownForObject(ipicker);
int hr = Marshal.QueryInterface(pIUnk, ref IID_IDsObjectPickerCredentials, out ptr);
if (hr == HRESULT.S_OK)
{
try
{
IDsObjectPickerCredentials cred = (IDsObjectPickerCredentials)Marshal.GetObjectForIUnknown(ptr);
hr = cred.SetCredentials(User, Password);
if (hr != HRESULT.S_OK)
{
System.Diagnostics.Debugger.Break(); // Fail
return (int)HRESULT.S_FALSE;
}
}
catch (Exception ex)
{
return (int)HRESULT.S_FALSE;
}
finally
{
Marshal.Release(ptr);
}
}
return (int)HRESULT.S_OK;
}
}
}
The IDsObjectPickerCredentials interface derives from the IDsObjectPicker. Your sample actually calls the Initialize method with a username and a password instead of SetCredentials.
Here is a correct declaration:
[ComImport, Guid("e2d3ec9b-d041-445a-8f16-4748de8fb1cf"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IDsObjectPickerCredentials
{
[PreserveSig]
int Initialize(ref DSOP_INIT_INFO pInitInfo);
[PreserveSig]
int InvokeDialog(IntPtr HWND, out IDataObject lpDataObject);
[PreserveSig]
int SetCredentials(string userName, string password);
}
Please note that instead of deriving from the IDsObjectPicker, the code replicates its methods, in the same order. This is necessary for .NET COM Interop.
You don't need that manual QueryInterface/Release calls, var cred = (IDsObjectPickerCredentials)iobjectpicker; is enough.
Also I found that the SetCredentials should be called before the Initialize method.

Using COM component fails as admin

I have a .NET wrapper over a native dll.
I try to register the component only for current user based on this : link
This runs fine, except if I right click the executable and say "Run as admin.." the tool will fail that it cannot find the class.
This is the code I use, is there something I am missing , I think it might be related to :
link
Thanks!
public int Register(ServerAction Action, string ServerName)
{
int hr = 0;
string szFunction = (Action == ServerAction.Register) ? "DllRegisterServer" : "DllUnregisterServer";
logger.Info("FileReader->Try to " + szFunction + " " + ServerName);
try
{
IntPtr hMod = LoadLibrary(ServerName);
if (hMod.ToInt32() != 0)
{
IntPtr registerDllServer_ProcAddress = GetProcAddress(hMod, szFunction);
DllRegisterServerInvoker pfnDllProc = (DllRegisterServerInvoker)Marshal.GetDelegateForFunctionPointer(registerDllServer_ProcAddress, typeof(DllRegisterServerInvoker));
if (pfnDllProc != null)
{
if (isUserAdminAndProcessElevated())
{
logger.Info("User is admin and process is elevated!");
}
else
{
logger.Info("User is not admin!");
hr = OverrideClassesRoot(HKEY_CURRENT_USER, "Software\\Classes");
}
}
hr = pfnDllProc();
pfnDllProc = null;
FreeLibrary(hMod);
}
else
{
hr = this.GetHresultFromWin32();
}
}
catch
{
Console.WriteLine("FileReader->" + szFunction + " " + ServerName + " FAILED!");
}
string errorMessage = new Win32Exception(hr).Message;
logger.Fatal(errorMessage);
return hr;
}
private Int32 OverrideClassesRoot(UIntPtr hKeyBase, string OverrideKey)
{
UIntPtr hkey = new UIntPtr();
int l = RegOpenKeyA(hKeyBase, OverrideKey, ref hkey);
if (l == 0)
{
l = RegOverridePredefKey(HKEY_CLASSES_ROOT, hkey);
RegCloseKey(hkey);
}
return this.GetHresultFromWin32();
}
Still haven't figured this out but I fixed it with : MSDN link

How to use MSMAPI32.ocx in WPF to send mail?

We are developing an desktop application based on WPF, which has a feature that user can send feedback to us. We can receive the Name, Email, Body and so on.
For some historical problems we used MSMAPI to help us send mail, as time goes on we found all its function work very well until we did tests on clean machine.
The ActiveX control said, please offer me a license so I can move on, well that's make us quite confused, because that control doesn't have license at all.
Ok, I will talk about the detail now.
In folder C:\Windows\SysWOW64 (On Win7 x64), we have msmapi32.ocx. From MSDN, we have to wrapper it so can be host on a Windows Form. We did it, and got two assemblies: MSMAPI.dll and AxMSMAPI.dll, so we make our application reference these two DLLs.
Here is our code(XAML):
<Window x:Class="WpfApplication14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ax="clr-namespace:AxMSMAPI;assembly=AxMSMAPI"
Title="MainWindow"
Width="525"
Height="350">
<StackPanel>
<WindowsFormsHost>
<ax:AxMAPIMessages Name="axMAPIMessage" />
</WindowsFormsHost>
<WindowsFormsHost>
<ax:AxMAPISession Name="axMAPISession" />
</WindowsFormsHost>
</StackPanel>
</Window>
Ofcourse, we have to change our project build target to X86, otherwise it would throw ClassNotDefined exception. Pretty well, press F5, the main window shows successfully.
Then we copy the Debug folder of our demo application, double click, Oops... It said, WpfAPplication14 has stopped work. We captured the exption, here is the stack:
System.Windows.Markup.XamlParseException occurred
_HResult=-2146233087
_message='Initialization of 'AxMSMAPI.AxMAPIMessages' threw an exception.' Line number '-' and line position '-'.
HResult=-2146233087
IsTransient=false
Message='Initialization of 'AxMSMAPI.AxMAPIMessages' threw an exception.' Line number '-' and line position '-'.
Source=PresentationFramework
LineNumber=-
LinePosition=-
StackTrace:
at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
InnerException: System.ComponentModel.LicenseException
_HResult=-2146232063
_message=You must have a license to use this ActiveX control.
HResult=-2146232063
IsTransient=false
Message=You must have a license to use this ActiveX control.
Source=System.Windows.Forms
StackTrace:
at System.Windows.Forms.AxHost.CreateInstance()
at System.Windows.Forms.AxHost.GetOcxCreate()
at System.Windows.Forms.AxHost.TransitionUpTo(Int32 state)
at System.Windows.Forms.AxHost.CreateHandle()
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.AxHost.EndInit()
at MS.Internal.Xaml.Runtime.ClrObjectRuntime.InitializationGuard(XamlType xamlType, Object obj, Boolean begin)
InnerException:
Then we wrote a Windows Forms application, in the beginning we did excatly the same with WPF demo, reference the two DLLs to project, and even the result is the same. It only can show window in our develpment machine, cannot run on clean Test Machine.
So we followed some steps on MSDN, add the ActiveX control (C:\Windows\SysWOW64\msmapi32.ocx) to Visual Studio Toolbox, and drag them to the surface, and it works very well on Test Machine.
The last try by us is, follow the Windows Forms demo did, we intended to add the ocx to toolbox, but the Visual Studio said it is successfully added to toolbox, but it is not enabled in the active designer due to incompatible with the current designer and .NET framework version (VS2012/.NET 4.0/WPF).
We don'n know what's the reason the demo window thrown exception when parse XAML in Test Machine, it seems we need a license, but does it need a license???
We tried to achieve it follow Windows Forms demo did, but we even cannot add the ocx to toolbox.
So, if you have some advices towards below questions or any of them, please feel free to let us know:
Where does the msmapi32.ocx come from? Does any windows machine has it or only after installed something. By the way, we also tried to install Office 2013 on our Test Machine, it sitll the same exception thrown.
We know our ocx version is 6.0, which released at 2000. So if we would like to use it in our WPF application, any suggestions?
What factors can affect this? we searched the Dev and Test machine, both have the ocx in the folder, and both registry have the MSAPI related item (and the Windows Forms works very well).
So if above questions can be answered, it can help us a lot. From your experience, how can you achieve this feature using WPF, using the native API provided by .NET or some else 3rd party control/plugin? In fact our requirement is so eary that user send feedback to some fixed address. That's all we need to do.
Very appreciate if you have some thoughts about our question, and feel free to ask us questions which puzzled you.
Thanks.
Did you try registering the ocx? (Make sure that a newer ocx is not already registered)
I had used native .NET APIs to send mails in the past. Here is a code snippet:
/// <summary>
///Method to Send an Email informing interested parties about the status of data extraction.
/// INPUTS : int iProcessIsSuccess : int informing about the success of the process. -1 means failure, 0 means partial success, 1 means success.
/// string szLogDataToBeSent : Log data to be sent incase process not successful.
/// OUTPUTS : bool. True if success, else false.
/// </summary>
public bool SendEmailNotification(string szEmailAddressFileName, int iProcessIsSuccess, string szLogDataToBeSent)
{
bool bSuccess = false;
//the the SMTP host.
SmtpClient client = new SmtpClient();
//SMTP Server
client.Host = CommonVariables.EMAIL_SMTP_SERVER;
//SMTP Credentials
client.Credentials = new NetworkCredential(CommonVariables.EMAIL_USERNAME, CommonVariables.EMAIL_PASSWORD);
//Creating a new mail.
MailMessage mail = new MailMessage();
//Filling 'From' Tab.
mail.From = new MailAddress(CommonVariables.EMAIL_SENDERS_ADDRESS, CommonVariables.EMAIL_SENDERS_NAME);
//Filling 'To' tab.
List<EmailAddress> EmailAddressList = new List<EmailAddress>();
try
{
using (System.IO.FileStream fs = new FileStream(szEmailAddressFileName, FileMode.Open))
{
XmlSerializer xs = new XmlSerializer(typeof(List<EmailAddress>));
EmailAddressList = xs.Deserialize(fs) as List<EmailAddress>;
}
foreach (EmailAddress addr in EmailAddressList)
{
mail.To.Add(addr.RecepientEmailAddress);
}
}
catch(Exception Ex)
{
mail.To.Add("somedefaultemail#companyname.com");
}
//Filling mail body.
string szMailBody = "";
string szMailSubject = "";
if (1 == iProcessIsSuccess) //Success
{
szMailSubject = String.Format(CommonVariables.EMAIL_SUBJECT_BOILER_PLATE, "a SUCCESS");
szMailBody = String.Format(CommonVariables.EMAIL_BODY_BOILER_PLATE, DateTime.UtcNow.ToString(), Environment.MachineName);
szMailBody += "\r\n" + szMailSubject + "\r\n";
}
else if (0 == iProcessIsSuccess) //Partially Success
{
szMailSubject = String.Format(CommonVariables.EMAIL_SUBJECT_BOILER_PLATE, "a PARTIAL SUCCESS"); ;
szMailBody = String.Format(CommonVariables.EMAIL_BODY_BOILER_PLATE, DateTime.UtcNow.ToString(), Environment.MachineName);
szMailBody += "\r\n"+ szMailSubject + "\r\n";
szMailBody += "\r\n" + "The Log data is as follows:\r\n";
szMailBody += szLogDataToBeSent;
mail.Priority = MailPriority.High;
}
else //Failed
{
szMailSubject = String.Format(CommonVariables.EMAIL_SUBJECT_BOILER_PLATE, "a FAILURE"); ;
szMailBody = String.Format(CommonVariables.EMAIL_BODY_BOILER_PLATE, DateTime.UtcNow.ToString(), Environment.MachineName);
szMailBody += "\r\n" + szMailSubject + "\r\n";
szMailBody += "\r\n" + "The Log data is as follows:\r\n";
szMailBody += szLogDataToBeSent;
mail.Priority = MailPriority.High;
}
mail.Body = szMailBody;
mail.Subject = szMailSubject;
//Send Email.
try
{
client.Send(mail);
bSuccess = true;
}
catch (Exception Ex)
{
bSuccess = false;
}
// Clean up.
mail.Dispose();
return bSuccess;
}
My post is not an answer to your questions, but provides a solution (if you like it).
In the past I've used this class (mapi compliant).
This class doesn't use native SmtpClient class (that sends an email but doesn't interact with your default mailing app) but is able to invoke your email application and popup email window (filled with every detail you need) so your customer can modify it as needed.
More, email sent remains is "sent email" folder...
enum HowTo { MAPI_ORIG = 0, MAPI_TO, MAPI_CC, MAPI_BCC };
public class MAPI
{
[DllImport("MAPI32.DLL")]
static extern int MAPISendMail(IntPtr sess, IntPtr hwnd, MapiMessage message, int flg, int rsv);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class MapiMessage
{
public int reserved;
public string subject;
public string noteText;
public string messageType;
public string dateReceived;
public string conversationID;
public int flags;
public IntPtr originator;
public int recipCount;
public IntPtr recips;
public int fileCount;
public IntPtr files;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class MapiFileDesc
{
public int reserved;
public int flags;
public int position;
public string path;
public string name;
public IntPtr type;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class MapiRecipDesc
{
public int reserved;
public int recipClass;
public string name;
public string address;
public int eIDSize;
public IntPtr entryID;
}
List<MapiRecipDesc> m_recipients = new List<MapiRecipDesc>();
List<string> m_attachments = new List<string>();
private int m_lastError = 0;
private const int MAPI_LOGON_UI = 0x00000001;
private const int MAPI_DIALOG = 0x00000008;
private const int maxAttachments = 20;
#region Private methods
private bool AddRecipients(HowTo howTo, params string[] emails)
{
bool ret = true;
foreach (string email in emails)
{
try
{
MapiRecipDesc recipient = new MapiRecipDesc();
recipient.recipClass = (int)howTo;
recipient.name = email;
m_recipients.Add(recipient);
}
catch { ret = false; }
}
return ret;
}
private int SendMail(string strSubject, string strBody, int how)
{
MapiMessage msg = new MapiMessage();
msg.subject = strSubject;
msg.noteText = strBody;
msg.recips = GetRecipients(out msg.recipCount);
msg.files = GetAttachments(out msg.fileCount);
m_lastError = MAPISendMail(new IntPtr(0), new IntPtr(0), msg, how, 0);
if (m_lastError > 1)
MessageBox.Show("MAPISendMail failed! " + LastError, "MAPISendMail");
Cleanup(ref msg);
return m_lastError;
}
private IntPtr GetRecipients(out int recipCount)
{
recipCount = 0;
if (m_recipients.Count == 0)
return IntPtr.Zero;
int size = Marshal.SizeOf(typeof(MapiRecipDesc));
IntPtr intPtr = Marshal.AllocHGlobal(m_recipients.Count * size);
int ptr = (int)intPtr;
foreach (MapiRecipDesc mapiDesc in m_recipients)
{
Marshal.StructureToPtr(mapiDesc, (IntPtr)ptr, false);
ptr += size;
}
recipCount = m_recipients.Count;
return intPtr;
}
private IntPtr GetAttachments(out int fileCount)
{
fileCount = 0;
if (m_attachments == null)
return IntPtr.Zero;
if ((m_attachments.Count <= 0) || (m_attachments.Count > maxAttachments))
return IntPtr.Zero;
int size = Marshal.SizeOf(typeof(MapiFileDesc));
IntPtr intPtr = Marshal.AllocHGlobal(m_attachments.Count * size);
MapiFileDesc mapiFileDesc = new MapiFileDesc();
mapiFileDesc.position = -1;
int ptr = (int)intPtr;
foreach (string strAttachment in m_attachments)
{
mapiFileDesc.name = Path.GetFileName(strAttachment);
mapiFileDesc.path = strAttachment;
Marshal.StructureToPtr(mapiFileDesc, (IntPtr)ptr, false);
ptr += size;
}
fileCount = m_attachments.Count;
return intPtr;
}
private void Cleanup(ref MapiMessage msg)
{
int size = Marshal.SizeOf(typeof(MapiRecipDesc));
int ptr = 0;
if (msg.recips != IntPtr.Zero)
{
ptr = (int)msg.recips;
for (int i = 0; i < msg.recipCount; i++)
{
Marshal.DestroyStructure((IntPtr)ptr, typeof(MapiRecipDesc));
ptr += size;
}
Marshal.FreeHGlobal(msg.recips);
}
if (msg.files != IntPtr.Zero)
{
size = Marshal.SizeOf(typeof(MapiFileDesc));
ptr = (int)msg.files;
for (int i = 0; i < msg.fileCount; i++)
{
Marshal.DestroyStructure((IntPtr)ptr, typeof(MapiFileDesc));
ptr += size;
}
Marshal.FreeHGlobal(msg.files);
}
m_recipients.Clear();
m_attachments.Clear();
m_lastError = 0;
}
#endregion
#region Public methods
public bool AddTo(params string[] emails)
{
return AddRecipients(HowTo.MAPI_TO, emails);
}
public bool AddCC(params string[] emails)
{
return AddRecipients(HowTo.MAPI_CC, emails);
}
public bool AddBCC(params string[] emails)
{
return AddRecipients(HowTo.MAPI_BCC, emails);
}
public void AddAttachment(string strAttachmentFileName)
{
m_attachments.Add(strAttachmentFileName);
}
public int SendMailPopup(string strSubject, string strBody)
{
return SendMail(strSubject, strBody, MAPI_LOGON_UI | MAPI_DIALOG);
}
public int SendMailDirect(string strSubject, string strBody)
{
return SendMail(strSubject, strBody, MAPI_LOGON_UI);
}
public string LastError
{
get
{
switch (m_lastError)
{
case 0: return "OK";
case 1: return "User abort [1]";
case 2: return "General MAPI failure [2]";
case 3: return "MAPI login failure [3]";
case 4: return "Disk full [4]";
case 5: return "Insufficient memory [5]";
case 6: return "Access denied [6]";
case 7: return "-unknown- [7]";
case 8: return "Too many sessions [8]";
case 9: return "Too many files were specified [9]";
case 10: return "Too many recipients were specified [10]";
case 11: return "A specified attachment was not found [11]";
case 12: return "Attachment open failure [12]";
case 13: return "Attachment write failure [13]";
case 14: return "Unknown recipient [14]";
case 15: return "Bad recipient type [15]";
case 16: return "No messages [16]";
case 17: return "Invalid message [17]";
case 18: return "Text too large [18]";
case 19: return "Invalid session [19]";
case 20: return "Type not supported [20]";
case 21: return "A recipient was specified ambiguously [21]";
case 22: return "Message in use [22]";
case 23: return "Network failure [23]";
case 24: return "Invalid edit fields [24]";
case 25: return "Invalid recipients [25]";
case 26: return "Not supported [26]";
default: return String.Format("MAPI error [{0}]", m_lastError);
}
}
}
#endregion
}

Get Product Code of installed Msi

I have a C# program where I have to get the product code of an installed msi. I have only the msi name as the input. Can this be done programmatically?
Do the answers to this question help? They want to get the product name, but maybe it works for the product code, too?
EDIT
If you do not have the MSI file itself to access the database (as suggested by the above link to the other question), you may try to search the following registry path for the name of your MSI file:
HKEY_CLASSES_ROOT\Installer\Products\*\SourceList
There are many entries under the Products branch. Each of them is a product key. Every branch should contain the SourceList node, which in turn should contain the value PackageName. That value holds the name of the MSI file.
So what I'd do is:
for each key in Products
{
open SourceList subkey
read PackageName value
if name equals my msi file name
{
return key-name formatted as GUID
}
}
This is the code I used to get the UninstallString of any MSI.
private string GetUninstallString(string msiName)
{
Utility.WriteLog("Entered GetUninstallString(msiName) - Parameters: msiName = " + msiName);
string uninstallString = string.Empty;
try
{
string path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products";
RegistryKey key = Registry.LocalMachine.OpenSubKey(path);
foreach (string tempKeyName in key.GetSubKeyNames())
{
RegistryKey tempKey = key.OpenSubKey(tempKeyName + "\\InstallProperties");
if (tempKey != null)
{
if (string.Equals(Convert.ToString(tempKey.GetValue("DisplayName")), msiName, StringComparison.CurrentCultureIgnoreCase))
{
uninstallString = Convert.ToString(tempKey.GetValue("UninstallString"));
uninstallString = uninstallString.Replace("/I", "/X");
uninstallString = uninstallString.Replace("MsiExec.exe", "").Trim();
uninstallString += " /quiet /qn";
break;
}
}
}
return uninstallString;
}
catch (Exception ex)
{
throw new ApplicationException(ex.Message);
}
}
This will give a result like this:
MsiExec.exe /I{6BB09011-69E1-472F-ACAD-FA0E7DA3E2CE}
From this string, you can take the substring within the braces {}, which will be 6BB09011-69E1-472F-ACAD-FA0E7DA3E2CE. I hope this might be the product code.
There is most fast and simply way - to use WMI with conditional query string.
public string GetProductCode(string productName)
{
string query = string.Format("select * from Win32_Product where Name='{0}'", productName);
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
{
foreach (ManagementObject product in searcher.Get())
return product["IdentifyingNumber"].ToString();
}
return null;
}
This code obtains the product code directly from an MSI file. So this allows reading the code without installing the file.
class MsiHandle : SafeHandleMinusOneIsInvalid
{
public MsiHandle()
: base(true)
{ }
protected override bool ReleaseHandle()
{
return NativeMethods.MsiCloseHandle(handle) == 0;
}
}
class NativeMethods
{
const string MsiDll = "Msi.dll";
[DllImport(MsiDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
public extern static uint MsiOpenPackageW(string szPackagePath, out MsiHandle product);
[DllImport(MsiDll, ExactSpelling=true)]
public extern static uint MsiCloseHandle(IntPtr hAny);
[DllImport(MsiDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
static extern uint MsiGetProductPropertyW(MsiHandle hProduct, string szProperty, StringBuilder value, ref int length);
[DllImport(MsiDll, ExactSpelling = true)]
public static extern int MsiSetInternalUI(int value, IntPtr hwnd);
public static uint MsiGetProductProperty(MsiHandle hProduct, string szProperty, out string value)
{
StringBuilder sb = new StringBuilder(1024);
int length = sb.Capacity;
uint err;
value = null;
if(0 == (err = MsiGetProductPropertyW(hProduct, szProperty, sb, ref length)))
{
sb.Length = length;
value = sb.ToString();
return 0;
}
return err;
}
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static int Main(string[] args)
{
string msiFile = args[0];
NativeMethods.MsiSetInternalUI(2, IntPtr.Zero); // Hide all UI. Without this you get a MSI dialog
MsiHandle msi;
uint err;
if (0 != (err = NativeMethods.MsiOpenPackageW(args[0], out msi)))
{
Console.Error.WriteLine("Can't open MSI, error {0}", err);
return 1;
}
// Strings available in all MSIs
string productCode;
using (msi)
{
if (0 != NativeMethods.MsiGetProductProperty(msi, "ProductCode", out productCode))
throw new InvalidOperationException("Can't obtain product code");
Console.WriteLine(productCode);
return 0;
}
}
}
Full example in Subversion on http://ankhsvn.open.collab.net/svn/ankhsvn/trunk/src/tools/Ankh.Chocolatey/
Use username 'guest' and no password.
private static bool GetUninstallString(string ProductName)
{
try
{
RegistryKey localKey = RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, RegistryView.Registry64);
var key = localKey.OpenSubKey(#"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") ??
localKey.OpenSubKey(
#"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall");
if (key == null)
return false;
return key.GetSubKeyNames()
.Select(keyName => key.OpenSubKey(keyName))
.Select(subkey => subkey.GetValue("DisplayName") as string)
.Any(displayName => displayName != null && displayName.Contains(ProductName));
}
catch
{
// Log message
return false;
}
}
This is very usefull for search string by productname

Categories