We have a scenario in which we are trying to open CRM in Winform WebBrowser control and every time we open it, it asks for authentication in pop up window.
Also from that when we are trying to open a new form (which opens in IE) it asks for authentication again.
The catch here is that users are logged in into machines (Windows 7, IE 9) through local credentials and we can’t use integrated authentication.
We have captured user credentials in application i.e. we know user name and password, Is there a way through which we can pass the credentials to Webbrowser control and IE so that it does not ask user for credentials.
Appreciate You Help.
Thanks.
Abhinav
If you log in from the browser and authenticate yourself, you will see the following cookies being stored:
Theoretically, retaining these cookies across requests should prevent crm from asking for authentication each time you open a new instance of the WebBrowser Control.
You can set Cookies for the control using the WebBrowser.Document.Cookie property.
To retrieve the cookie after authenticating the first time, you will need to use the following code:
[SecurityCritical]
public static string GetCookieInternal(Uri uri, bool throwIfNoCookie)
{
uint pchCookieData = 0;
string url = UriToString(uri);
uint flag = (uint)NativeMethods.InternetFlags.INTERNET_COOKIE_HTTPONLY;
//Gets the size of the string builder
if (NativeMethods.InternetGetCookieEx(url, null, null, ref pchCookieData, flag, IntPtr.Zero))
{
pchCookieData++;
StringBuilder cookieData = new StringBuilder((int)pchCookieData);
//Read the cookie
if (NativeMethods.InternetGetCookieEx(url, null, cookieData, ref pchCookieData, flag, IntPtr.Zero))
{
DemandWebPermission(uri);
return cookieData.ToString();
}
}
int lastErrorCode = Marshal.GetLastWin32Error();
if (throwIfNoCookie || (lastErrorCode != (int)NativeMethods.ErrorFlags.ERROR_NO_MORE_ITEMS))
{
throw new Win32Exception(lastErrorCode);
}
return null;
}
private static void DemandWebPermission(Uri uri)
{
string uriString = UriToString(uri);
if (uri.IsFile)
{
string localPath = uri.LocalPath;
new FileIOPermission(FileIOPermissionAccess.Read, localPath).Demand();
}
else
{
new WebPermission(NetworkAccess.Connect, uriString).Demand();
}
}
private static string UriToString(Uri uri)
{
if (uri == null)
{
throw new ArgumentNullException("uri");
}
UriComponents components = (uri.IsAbsoluteUri ? UriComponents.AbsoluteUri : UriComponents.SerializationInfoString);
return new StringBuilder(uri.GetComponents(components, UriFormat.SafeUnescaped), 2083).ToString();
}
internal sealed class NativeMethods
{
#region enums
public enum ErrorFlags
{
ERROR_INSUFFICIENT_BUFFER = 122,
ERROR_INVALID_PARAMETER = 87,
ERROR_NO_MORE_ITEMS = 259
}
public enum InternetFlags
{
INTERNET_COOKIE_HTTPONLY = 8192, //Requires IE 8 or higher
INTERNET_COOKIE_THIRD_PARTY = 131072,
INTERNET_FLAG_RESTRICTED_ZONE = 16
}
#endregion
#region DLL Imports
[SuppressUnmanagedCodeSecurity, SecurityCritical, DllImport("wininet.dll", EntryPoint = "InternetGetCookieExW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]internal static extern bool InternetGetCookieEx([In] string Url, [In] string cookieName, [Out] StringBuilder cookieData, [In, Out] ref uint pchCookieData, uint flags, IntPtr reserved);
#endregion
}
I usually refrain from posting code I haven't tried out myself but I don't have access to an On-Prem environment at the moment. I would love to know if this works so let me know if you try it out.
Related
I need to digitally sign PDF documents on a web server running ASP.NET Core 2.2 in IIS. The web app is running with a service user and the impersonation shall be done in the code. The problem is, I can't access the users certificates via the X509Store class. I tried to create a minimal example which only impersonates a different user and outputs the certificates in the console. I ran it as an administrator but no certifiactes were found.
Permissions I granted in the "Local Group Policy Editor" (gpedit.msc) under "Computer Configuration > Windows Settings > Security Settings > Local Policies > User Rights Management":
Act as part of the operating system: Added ServiceUser
Allow log on locally: Added UserToBeImpersonated
Log on as batch job: Added UserToBeImpersonated
Create a token object: Added ServiceUser
Replace a process level token: Added ServiceUser
Adjust Memory Quotas for a process: Added ServiceUser
For the impersonation I used the SimpleImpersonation from https://github.com/mj1856/SimpleImpersonation and extended it to also load the user profile like this:
public static void RunAsUser(UserCredentials credentials, LogonType logonType, Action action)
{
// this method tells Windows to dynamically determine where to look for the HKEY_CURRENT_USER registry hive,
// rather than using the cached location from when the process was initially invoked
RegDisablePredefinedCache();
using (var tokenHandle = credentials.Impersonate(logonType))
using (var profileToken = credentials.ImpersonateUserProfile(tokenHandle.DangerousGetHandle()))
{
RunImpersonated(tokenHandle, _ => action());
}
}
internal UserProfileToken ImpersonateUserProfile(IntPtr tokenHandle)
{
var tokenDuplicate = IntPtr.Zero;
// Not sure if the token needs to be duplicated or not
if (DuplicateToken(tokenHandle, 2, ref tokenDuplicate) == 0)
HandleError(tokenHandle);
// Load User profile
var profileInfo = new ProfileInfo();
profileInfo.dwSize = Marshal.SizeOf(profileInfo);
profileInfo.lpUserName = _username;
profileInfo.dwFlags = 1;
// LoadUserProfile() failed
if (!LoadUserProfile(tokenDuplicate, ref profileInfo))
HandleError(tokenDuplicate);
// LoadUserProfile() failed - HKCU handle was not loaded
if (profileInfo.hProfile == IntPtr.Zero)
HandleError(tokenDuplicate);
return new UserProfileToken() { ProfileInfo = profileInfo, Token = tokenDuplicate };
}
public class UserProfileToken : IDisposable
{
public ProfileInfo ProfileInfo { get; set; }
public IntPtr Token { get; set; }
~UserProfileToken()
{
Dispose();
}
private bool isDisposed = false;
public void Dispose()
{
if (isDisposed) return;
isDisposed = true;
UnloadUserProfile(Token, ProfileInfo.hProfile);
}
}
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);
[DllImport("Userenv.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool UnloadUserProfile(IntPtr hToken, IntPtr lpProfileInfo);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegDisablePredefinedCache();
The main code for the testing looks like this:
var credentials = new Impersonator.UserCredentials(domain, username, password);
Impersonator.RunAsUser(credentials, Impersonator.LogonType.Interactive, () =>
{
Console.WriteLine($"Current user: {WindowsIdentity.GetCurrent().Name}");
var store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
if (store.Certificates.Count == 0)
{
Console.WriteLine("No Certificates found.");
}
else
{
var certCount = 0;
foreach (var cert in store.Certificates)
{
Console.WriteLine($"[{certCount}] Subject: {cert.Subject}");
Console.WriteLine($"[{certCount}] Issuer: {cert.Issuer}");
certCount++;
}
}
});
Unfortunately no certificats are found when the same code (without impersonation) shows two certificates on the target users machine.
Another interesting thing I found on the server is that the users profile directory under C:\Users<username> has not the users name but some Chinese? characters. When checking the user profiles in the system settings the username is correct though.
Seems like the impersonation (with loading the profile) does not trigger the Auto-Enrollment for receiving the certificate from the certificate authority. This must be done seperately with something like this:
using CERTENROLLLib;
public static void EnrollUserCertificateByTemplate(string templateName, string subjectName, string friendlyName = null)
{
var enrollment = new CX509Enrollment();
// Set target store
enrollment.InitializeFromTemplateName(X509CertificateEnrollmentContext.ContextUser, templateName);
var request = enrollment.Request;
var innerRequest = request.GetInnerRequest(InnerRequestLevel.LevelInnermost);
var innerRequestPkcs10 = innerRequest as IX509CertificateRequestPkcs10;
// Set the subject name
var distinguishedName = new CX500DistinguishedName();
distinguishedName.Encode(subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
innerRequestPkcs10.Subject = distinguishedName;
// Set the friendly name
if (friendlyName != null) enrollment.CertificateFriendlyName = friendlyName;
// Enroll for the certificate into MY store if it is successfully issued by CA
enrollment.Enroll();
}
Additionaly you need to check if there is already a valid certificate available in the users certificate store. Otherwise you add a certificate everytime you impersonate (and execute the code above).
Anothr thing I needed to do was giving Administrative rights to the executing user. All the other permissions posted in my question could be remove.
I can't seem to start a process as another user when using impersonation under .Net Core.
I'm running this script in Linqpad running as User1 and trying to launch a program as User2.
At first, the impersonation seems to work (the Console.Writeline()s on the current user change correctly from User1 to User2 in the RunImpersonated() Method). However, the process always runs as User1.
This is one of many tests I'm doing to validate that RunImpersonated() works (this originally stems from issues in an ASP.Net Core App trying to impersonate the current user). This is the simplest reproducible example I could find.
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);
void Main()
{
string domainName = "myDomain";
string userName = "User2";
string passWord = "User2Password";
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
// Call LogonUser to obtain a handle to an access token.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, passWord,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeAccessTokenHandle);
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
Console.WriteLine("LogonUser failed with error code : {0}", ret);
throw new System.ComponentModel.Win32Exception(ret);
}
Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
// Check the identity.
Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);
// Note: if you want to run as unimpersonated, pass
// 'SafeAccessTokenHandle.InvalidHandle' instead of variable 'safeAccessTokenHandle'
WindowsIdentity.RunImpersonated(
safeAccessTokenHandle,
// User action
() =>
{
// Check the identity.
Console.WriteLine("During impersonation: " + WindowsIdentity.GetCurrent().Name);
Directory.GetFiles(#"C:\TMP\").Dump();
var pi = new ProcessStartInfo
{
WorkingDirectory = #"C:\TMP\",
FileName = #"C:\TMP\TestUser.exe"
};
var proc = Process.Start(pi);
proc.WaitForExit();
}
);
// Check the identity again.
Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);
}
If you don't specify username and password, Process.Start will uses the token for the calling process, not the impersonation token.
Looking into source code of Process.Start:
If you pass user name and password it uses CreateProcess
If you don't specify username and password, it calls CreateProcessWithLogon
If the calling process is impersonating another user, the new process
uses the token for the calling process, not the impersonation token.
To run the new process in the security context of the user represented
by the impersonation token, use the CreateProcessAsUser or
CreateProcessWithLogonW function.
Without passing username and password the process always runs on security context of the original process owner. If you want to run the process under the context of another user:
If you have username and password: use CreateProcessWithLogon
If you have a to the primary token that represents a user use CreateProcessAsUser
There are multiple impersonation levels. Documentation states that you cannot use thread impersonation token to start the process - primary token will always be used:
The system uses the primary token of the process rather than the
impersonation token of the calling thread in the following situations:
If an impersonating thread calls the CreateProcess function, the new
process always inherits the primary token of the process.
Given you don't have user's password and you want to use impersonation token instead to start a process, unfortunately, the answer is - you can't do that.
You can impersonate caller for other operations though (RPC, COM, FS...).
A while back I used the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
using System.Text;
using System.Windows;
using Microsoft.Win32.SafeHandles;
namespace ZZZ
{
partial class User : IDisposable
{
private string m_domain;
private string m_user;
private string m_pass;
private WindowsIdentity user;
private SafeTokenHandle safeTokenHandle;
private WindowsImpersonationContext impContext;
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out SafeTokenHandle phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool RevertToSelf();
//For the current user
public User()
{
user = WindowsIdentity.GetCurrent();
}
// For custom user
public User(string domain, string user, string password, bool doImepsonate = false)
{
m_domain = domain;
m_user = user;
m_pass = password;
if (doImepsonate) this.Impersonate();
}
// If it's intended to incorporate this code into a DLL, then demand FullTrust.
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public void Impersonate()
{
if (impContext != null) throw new ImpersonationException();
try
{
// Get the user token for the specified user, domain, and password using the unmanaged LogonUser method.
// The local machine name can be used for the domain name to impersonate a user on this machine.
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(
m_user,
m_domain,
m_pass,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
if (returnValue == false)
{
int ret = Marshal.GetLastWin32Error();
throw new Win32Exception(ret);
}
using (safeTokenHandle)
{
// Use the token handle returned by LogonUser.
user = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
impContext = user.Impersonate();
}
}
catch (Exception ex)
{
MessageBox.Show("Exception occurred:\n" + ex.Message);
}
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
private void Quit()
{
if (impContext == null) return;
impContext.Undo();
safeTokenHandle.Dispose();
}
#endregion
internal IEnumerable<string> Groups
{
get
{
return user.Groups.Select(p =>
{
IdentityReference ir = null;
try { ir = p.Translate(typeof(NTAccount)); }
catch { }
return ir == null ? null : ir.Value;
});
}
}
}
// Win32 API part
internal sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle() : base(true) { }
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
internal sealed class ImpersonationException : Exception
{
public ImpersonationException() : base("The user is already impersonated.") { }
}
}
This works great for me. Check the user who is running the process. Sometime the user is not administrator or cannot impersonate.
Processinfo creates new process. Try process.start, Or you can convert exe to util dll and run inside code like utli.testuser code. Use dll call method from main program and not exe.
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
// Call LogonUser to obtain a handle to an access token.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeAccessTokenHandle);
WindowsIdentity.RunImpersonated(safeAccessTokenHandle, () => {
Var impersonatedUser = WindowsIdentity.GetCurrent().Name;
//--- Call your Method here…….
});
I am using windows functionality, impersonate, to change the active user before starting Chrome driver. Now, starting the driver without the impersonate code works fine. Also the impersonate code works fine; I see the user is changed. But when this change happens and after that I run IWebDriver driver=new ChromeDriver then the exception is triggered on that exact code and the test stops. Any ideas why this happens?
Here is main part of the code used (the code is just little modified code from another post here at stackoverflow)
namespace localSeleniumTest.Impersonation
{
class Program
{
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(stringpszUsername, string pszDomain, string pszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
// closes open handes returned by LogonUser
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
public void Impersonation()
{
WindowsImpersonationContext impersonationContext = null;
IntPtr userHandle = IntPtr.Zero;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
string domain = Config.domain;
string user = Config.username;
string password = Config.password;
try
{
String currentName = WindowsIdentity.GetCurrent().Name;
// if domain name was blank, assume local machine
if (domain == "")
domain = System.Environment.MachineName;
// Call LogonUser to get a token for the user
bool loggedOn = LogonUser(
user,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref userHandle
);
if (!loggedOn)
{
return;
}
// Begin impersonating the user
impersonationContext = WindowsIdentity.Impersonate(userHandle);
String afterImpersonationName = WindowsIdentity.GetCurrent().Name;
/*this few lines below does not work after impersonation but
work perfectly without the code above.*/
IWebDriver driver = new ChromeDriver();
driver.Navigate().GoToUrl("www.google.com");
System.Threading.Thread.Sleep(6000);
driver.Quit();
Found the issue. D user did not have permission to chrome driver i Bin folder
I have a windows service that runs under the local machine system account. Inside this service, it tries to read a remote .ini file that is available on a remote shared folder. The code trying to read this file is wrapped in impersonation using LogonUser (a simplified version of the code is below for an idea of what it is doing). The impersonation successfully starts impersonating the user configured, however the instant it attempts to read the remote ini file found on the remote network share, an UnauthorizedAccessException is thrown. This happens even though the configured user has read/write permissions on the remote machine. When I modify the code to remove all impersonation, and instead run the windows service as the configured user, all access to the remote .ini file are successful. I would prefer to use impersonation to get access to this file rather than a hack such as running the service as the user. Can anyone see errors with the impersonation code in the example? Is there something I need to do on my Vista 64 bit box to enable this? Are there specific active directory permissions my IT co-workers need to enable?
private WindowsImpersonationContext _impersonatedUser;
private IntPtr _token;
// Declare signatures for Win32 LogonUser and CloseHandle APIs
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(
string principal,
string authority,
string password,
LogonSessionType logonType,
LogonProvider logonProvider,
out IntPtr token);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);
enum LogonSessionType : uint
{
Interactive = 2,
Network,
Batch,
Service,
NetworkCleartext = 8,
NewCredentials
}
enum LogonProvider : uint
{
Default = 0, // default for platform (use this!)
WinNT35, // sends smoke signals to authority
WinNT40, // uses NTLM
WinNT50 // negotiates Kerb or NTLM
}
....
var result = LogonUser(exchangeUserId, exchangeDomain,
password,
LogonSessionType.Network,
LogonProvider.Default,
out _token);
var id = new WindowsIdentity(_token);
_impersonatedUser = id.Impersonate();
try
{
//Validate access to the file on the remote computer/share
File.GetAccessControl(remoteFileAvailableInSharedFolder);
}
catch (UnauthorizedAccessException unauthorized)
{
...
}
....
// Stop impersonation and revert to the process identity
if (_impersonatedUser != null)
{
_impersonatedUser.Undo();
_impersonatedUser = null;
}
if (_token != IntPtr.Zero)
{
CloseHandle(_token);
_token = IntPtr.Zero;
}
Doing further research I discovered the core issue to be caused by the impersonation code. I had to add the use of the api DuplicateToken and added usage of the api RevertToSelf too. On making these changes (see class below) the impersonation worked as well as the code did when the whole service was ran under my domain user. Hope the code helps others with this same issue.
public class Impersonation : IDisposable
{
private WindowsImpersonationContext _impersonatedUserContext;
// Declare signatures for Win32 LogonUser and CloseHandle APIs
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(
string principal,
string authority,
string password,
LogonSessionType logonType,
LogonProvider logonProvider,
out IntPtr token);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool RevertToSelf();
// ReSharper disable UnusedMember.Local
enum LogonSessionType : uint
{
Interactive = 2,
Network,
Batch,
Service,
NetworkCleartext = 8,
NewCredentials
}
// ReSharper disable InconsistentNaming
enum LogonProvider : uint
{
Default = 0, // default for platform (use this!)
WinNT35, // sends smoke signals to authority
WinNT40, // uses NTLM
WinNT50 // negotiates Kerb or NTLM
}
// ReSharper restore InconsistentNaming
// ReSharper restore UnusedMember.Local
/// <summary>
/// Class to allow running a segment of code under a given user login context
/// </summary>
/// <param name="user">domain\user</param>
/// <param name="password">user's domain password</param>
public Impersonation(string user, string password)
{
var token = ValidateParametersAndGetFirstLoginToken(user, password);
var duplicateToken = IntPtr.Zero;
try
{
if (DuplicateToken(token, 2, ref duplicateToken) == 0)
{
throw new Exception("DuplicateToken call to reset permissions for this token failed");
}
var identityForLoggedOnUser = new WindowsIdentity(duplicateToken);
_impersonatedUserContext = identityForLoggedOnUser.Impersonate();
if (_impersonatedUserContext == null)
{
throw new Exception("WindowsIdentity.Impersonate() failed");
}
}
finally
{
if (token != IntPtr.Zero)
CloseHandle(token);
if (duplicateToken != IntPtr.Zero)
CloseHandle(duplicateToken);
}
}
private static IntPtr ValidateParametersAndGetFirstLoginToken(string user, string password)
{
if (string.IsNullOrEmpty(user))
{
throw new ConfigurationErrorsException("No user passed into impersonation class");
}
var userHaves = user.Split('\\');
if (userHaves.Length != 2)
{
throw new ConfigurationErrorsException("User must be formatted as follows: domain\\user");
}
if (!RevertToSelf())
{
throw new Exception("RevertToSelf call to remove any prior impersonations failed");
}
IntPtr token;
var result = LogonUser(userHaves[1], userHaves[0],
password,
LogonSessionType.Interactive,
LogonProvider.Default,
out token);
if (!result)
{
throw new ConfigurationErrorsException("Logon for user " + user + " failed.");
}
return token;
}
public void Dispose()
{
// Stop impersonation and revert to the process identity
if (_impersonatedUserContext != null)
{
_impersonatedUserContext.Undo();
_impersonatedUserContext = null;
}
}
}
How can I run an EXE program from a Windows Service using C#?
This is my code:
System.Diagnostics.Process.Start(#"E:\PROJECT XL\INI SQLLOADER\ConsoleApplication2\ConsoleApplication2\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe");
When I run this service, the application is not starting.
What's wrong with my code?
This will never work, at least not under Windows Vista or later. The key problem is that you're trying to execute this from within a Windows Service, rather than a standard Windows application. The code you've shown will work perfectly in a Windows Forms, WPF, or Console application, but it won't work at all in a Windows Service.
Windows Services cannot start additional applications because they are not running in the context of any particular user. Unlike regular Windows applications, services are now run in an isolated session and are prohibited from interacting with a user or the desktop. This leaves no place for the application to be run.
More information is available in the answers to these related questions:
How can a Windows Service start a process when a Timer event is raised?
which process in windows is user specific?
windows service (allow service to interact with desktop)
The best solution to your problem, as you've probably figured out by now, is to create a standard Windows application instead of a service. These are designed to be run by a particular user and are associated with that user's desktop. This way, you can run additional applications whenever you want, using the code that you've already shown.
Another possible solution, assuming that your Console application does not require an interface or output of any sort, is to instruct the process not to create a window. This will prevent Windows from blocking the creation of your process, because it will no longer request that a Console window be created. You can find the relevant code in this answer to a related question.
i have tried this article Code Project, it is working fine for me.
I have used the code too. article is excellent in explanation with screenshot.
I am adding necessary explanation to this scenario
You have just booted up your computer and are about to log on. When you log on, the system assigns you a unique Session ID. In Windows Vista, the first User to log on to the computer is assigned a Session ID of 1 by the OS. The next User to log on will be assigned a Session ID of 2. And so on and so forth. You can view the Session ID assigned to each logged on User from the Users tab in Task Manager.
But your windows service is brought under session ID of 0. This session is isolated from other sessions. This ultimately prevent the windows service to invoke the application running under user session's like 1 or 2.
In order to invoke the application from windows service you need to copy the control from winlogon.exe which acts as present logged user as shown in below screenshot.
Important codes
// obtain the process id of the winlogon process that
// is running within the currently active session
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
winlogonPid = (uint)p.Id;
}
}
// obtain a handle to the winlogon process
hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// obtain a handle to the access token of the winlogon process
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
CloseHandle(hProcess);
return false;
}
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
// I would prefer to not have to use a security attribute variable and to just
// simply pass null and inherit (by default) the security attributes
// of the existing token. However, in C# structures are value types and therefore
// cannot be assigned the null value.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// copy the access token of the winlogon process;
// the newly created token will be a primary token
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
{
CloseHandle(hProcess);
CloseHandle(hPToken);
return false;
}
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
// interactive window station parameter; basically this indicates
// that the process created can display a GUI on the desktop
si.lpDesktop = #"winsta0\default";
// flags that specify the priority and creation method of the process
int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
// create a new process in the current User's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
applicationName, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
IntPtr.Zero, // pointer to new environment block
null, // name of current directory
ref si, // pointer to STARTUPINFO structure
out procInfo // receives information about new process
);
you can use from windows task scheduler for this purpose, there are many libraries like TaskScheduler that help you.
for example consider we want to scheduling a task that will executes once five seconds later:
using (var ts = new TaskService())
{
var t = ts.Execute("notepad.exe")
.Once()
.Starting(DateTime.Now.AddSeconds(5))
.AsTask("myTask");
}
notepad.exe will be executed five seconds later.
for details and more information please go to wiki
if you know which class and method in that assembly you need, you can invoke it yourself like this:
Assembly assembly = Assembly.LoadFrom("yourApp.exe");
Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
if (t.Name == "YourClass")
{
MethodInfo method = t.GetMethod("YourMethod",
BindingFlags.Public | BindingFlags.Instance);
if (method != null)
{
ParameterInfo[] parameters = method.GetParameters();
object classInstance = Activator.CreateInstance(t, null);
var result = method.Invoke(classInstance, parameters.Length == 0 ? null : parameters);
break;
}
}
}
Top answer with most upvotes isn't wrong but still the opposite of what I would post. I say it will totally work to start an exe file and you can do this in the context of any user. Logically you just can't have any user interface or ask for user input...
Here is my advice:
Create a simple Console Application that does what your service should do right on start without user interaction. I really recommend not using the Windows Service project type especially because you (currently) can't using .NET Core.
Add code to start your exe you want to call from service
Example to start e.g. plink.exe. You could even listen to the output:
var psi = new ProcessStartInfo()
{
FileName = "./Client/plink.exe", //path to your *.exe
Arguments = "-telnet -P 23 127.0.0.1 -l myUsername -raw", //arguments
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true //no window, you can't show it anyway
};
var p = Process.Start(psi);
Use NSSM (Non-Sucking Service Manager) to register that Console Application as service. NSSM can be controlled via command line and can show an UI to configure the service or you configure it via command line. You can run the service in the context of any user if you know the login data of that user.
I took LocalSystem account which is default and more than Local Service. It worked fine without having to enter login information of a specific user. I didn't even tick the checkbox "Allow service to interact with desktop" which you could if you need higher permissions.
Lastly I just want to say how funny it is that the top answer says quite the opposite of my answer and still both of us are right it's just how you interpret the question :-D. If you now say but you can't with the windows service project type - You CAN but I had this before and installation was sketchy and it was maybe kind of an unintentional hack until I found NSSM.
You should check this article Impact of Session 0 Isolation on Services and Drivers in Windows and download the .docx file and READ IT carefully , it was very helpful for me.
However this is a class which works fine for my case :
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
internal enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
internal enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public static class ProcessAsUser
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
private static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(
ref IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool DestroyEnvironmentBlock(
IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(
IntPtr hObject);
private const short SW_SHOW = 5;
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private const int GENERIC_ALL_ACCESS = 0x10000000;
private const int STARTF_USESHOWWINDOW = 0x00000001;
private const int STARTF_FORCEONFEEDBACK = 0x00000040;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = #"WinSta0\Default"; //Modify as needed
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
si.wShowWindow = SW_SHOW;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
FilesUtilities.WriteLog(message,FilesUtilities.ErrorType.Info);
}
return result;
}
private static IntPtr GetPrimaryToken(int processId)
{
IntPtr token = IntPtr.Zero;
IntPtr primaryToken = IntPtr.Zero;
bool retVal = false;
Process p = null;
try
{
p = Process.GetProcessById(processId);
}
catch (ArgumentException)
{
string details = String.Format("ProcessID {0} Not Available", processId);
FilesUtilities.WriteLog(details, FilesUtilities.ErrorType.Info);
throw;
}
//Gets impersonation token
retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
if (retVal == true)
{
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.nLength = (uint)Marshal.SizeOf(sa);
//Convert the impersonation token into Primary token
retVal = DuplicateTokenEx(
token,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref primaryToken);
//Close the Token that was previously opened.
CloseHandle(token);
if (retVal == false)
{
string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
}
else
{
string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
//We'll Close this token after it is used.
return primaryToken;
}
private static IntPtr GetEnvironmentBlock(IntPtr token)
{
IntPtr envBlock = IntPtr.Zero;
bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
if (retVal == false)
{
//Environment Block, things like common paths to My Documents etc.
//Will not be created if "false"
//It should not adversley affect CreateProcessAsUser.
string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
return envBlock;
}
public static bool Launch(string appCmdLine /*,int processId*/)
{
bool ret = false;
//Either specify the processID explicitly
//Or try to get it from a process owned by the user.
//In this case assuming there is only one explorer.exe
Process[] ps = Process.GetProcessesByName("explorer");
int processId = -1;//=processId
if (ps.Length > 0)
{
processId = ps[0].Id;
}
if (processId > 1)
{
IntPtr token = GetPrimaryToken(processId);
if (token != IntPtr.Zero)
{
IntPtr envBlock = GetEnvironmentBlock(token);
ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
if (envBlock != IntPtr.Zero)
DestroyEnvironmentBlock(envBlock);
CloseHandle(token);
}
}
return ret;
}
}
And to execute , simply call like this :
string szCmdline = "AbsolutePathToYourExe\\ExeNameWithoutExtension";
ProcessAsUser.Launch(szCmdline);
You can execute an .exe from a Windows service very well in Windows XP. I have done it myself in the past.
You need to make sure you had checked the option "Allow to interact with the Desktop" in the Windows service properties. If that is not done, it will not execute.
I need to check in Windows 7 or Vista as these versions requires additional security privileges so it may throw an error, but I am quite sure it can be achieved either directly or indirectly. For XP I am certain as I had done it myself.
First, we are going to create a Windows Service that runs under the
System account. This service will be responsible for spawning an
interactive process within the currently active User’s Session. This
newly created process will display a UI and run with full admin
rights. When the first User logs on to the computer, this service will
be started and will be running in Session0; however the process that
this service spawns will be running on the desktop of the currently
logged on User. We will refer to this service as the LoaderService.
Next, the winlogon.exe process is responsible for managing User login
and logout procedures. We know that every User who logs on to the
computer will have a unique Session ID and a corresponding
winlogon.exe process associated with their Session. Now, we mentioned
above, the LoaderService runs under the System account. We also
confirmed that each winlogon.exe process on the computer runs under
the System account. Because the System account is the owner of both
the LoaderService and the winlogon.exe processes, our LoaderService
can copy the access token (and Session ID) of the winlogon.exe process
and then call the Win32 API function CreateProcessAsUser to launch a
process into the currently active Session of the logged on User. Since
the Session ID located within the access token of the copied
winlogon.exe process is greater than 0, we can launch an interactive
process using that token.
Try this one.
Subverting Vista UAC in Both 32 and 64 bit Architectures
I think You are copying the .exe to different location. This might be the problem I guess. When you copy the exe, you are not copying its dependencies.
So, what you can do is, put all dependent dlls in GAC so that any .net exe can access it
Else, do not copy the exe to new location. Just create a environment variable and call the exe in your c#. Since the path is defined in environment variables, the exe is can be accessed by your c# program.
Update:
previously I had some kind of same issue in my c#.net 3.5 project in which I was trying to run a .exe file from c#.net code and that exe was nothing but the another project exe(where i added few supporting dlls for my functionality) and those dlls methods I was using in my exe application. At last I resolved this by creating that application as a separate project to the same solution and i added that project output to my deployment project. According to this scenario I answered, If its not what he wants then I am extremely sorry.
System.Diagnostics.Process.Start("Exe Name");