Changing the profile associated with a process is not obvious. The win32 APIs contain functions to do that: theoretically you could -after the impersonation for the current process started- respectively call ImpersonateLoggedOnUser to put the security context on the thread, LoadUserProfile to load the User Hive and CreateEnvironmentBlock to get the environment variables for the specified user. Unfortunately you need to have an unrealistic set of permissions to make this work (other than to a specific account for a Windows Service, I would be reluctant to assign 'Administrator' as well as 'Part of the Operating System' privileges).
So when you want to load a specific account's user profile, it makes probably more sense to simply start another process under that account. The ProcessStartInfo structure -used as parameter of .NET's Process.Start() method- has all the necessary attributes to make sure the account's profile is loaded.
Here's some sample code; it's the Main method of a console application that restarts itself under another account:
////
// Process Restart Demo: Restart process under another account
////
Console.Write("User Name: ");
Console.WriteLine(WindowsIdentity.GetCurrent().Name);
Console.Write("Application Directory: ");
Console.WriteLine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));
// Create Impersonation Object
Impersonation impersonation = new Impersonation("YourDomain", "Test123", "Test123");
// Ask for Restart
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write("\nReady to restart (Y/N): ");
ConsoleKeyInfo key = Console.ReadKey();
if ((key.KeyChar == 'y') || (key.KeyChar == 'Y'))
{
// Start Process
try
{
impersonation.StartProcess(Assembly.GetExecutingAssembly().Location);
}
catch (Exception ex)
{
// Oops
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("\n\n" + ex.Message);
Console.ReadKey();
}
}
Here's the full Impersonation class:
//-----------------------------------------------------------------------
// <copyright file="Impersonation.cs" company="DockOfTheBay">
// http://www.dotbay.be
// </copyright>
// <summary>Defines the Impersonation class.</summary>
//-----------------------------------------------------------------------
namespace DockOfTheBay
{
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
/// <summary>
/// Facilitates impersonation of a Windows User.
/// </summary>
public class Impersonation
{
/// <summary>
/// The User Id.
/// </summary>
private string userName = string.Empty;
/// <summary>
/// The Domain.
/// </summary>
private string domain = string.Empty;
/// <summary>
/// The Password.
/// </summary>
private SecureString password;
/// <summary>
/// Windows Token.
/// </summary>
private IntPtr tokenHandle = new IntPtr(0);
/// <summary>
/// The impersonated User.
/// </summary>
private WindowsImpersonationContext impersonatedUser;
/// <summary>
/// Initializes a new instance of the Impersonation class.
/// </summary>
/// <param name="domainName">Domain name of the impersonated user.</param>
/// <param name="userName">Name of the impersonated user.</param>
/// <param name="password">Password of the impersonated user.</param>
/// <remarks>
/// Uses the unmanaged LogonUser function to get the user token for
/// the specified user, domain, and password.
/// </remarks>
public Impersonation(string domainName, string userName, string password)
{
// Fill private field for later use
this.domain = domainName;
this.userName = userName;
this.password = new SecureString();
char[] passwordChars = password.ToCharArray();
foreach (char c in passwordChars)
{
this.password.AppendChar(c);
}
// Use the standard logon provider.
const int LOGON32_PROVIDER_DEFAULT = 0;
// Create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
this.tokenHandle = IntPtr.Zero;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(
userName,
domainName,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref this.tokenHandle);
if (false == returnValue)
{
// Something went wrong.
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
}
/// <summary>
/// Starts the impersonation.
/// </summary>
public void Impersonate()
{
// Create Identity.
WindowsIdentity newId = new WindowsIdentity(this.tokenHandle);
// Start impersonating.
this.impersonatedUser = newId.Impersonate();
}
/// <summary>
/// Stops the impersonation and releases security token.
/// </summary>
public void Revert()
{
// Stop impersonating.
if (this.impersonatedUser != null)
{
this.impersonatedUser.Undo();
}
// Release the token.
if (this.tokenHandle != IntPtr.Zero)
{
CloseHandle(this.tokenHandle);
}
}
/// <summary>
/// Spawns a process under a specific account.
/// </summary>
/// <param name="fullPath">Full path to the executable.</param>
/// <remarks>There's no need to call 'Impersonate' first.</remarks>
public void StartProcess(string fullPath)
{
ProcessStartInfo info = new ProcessStartInfo();
// File properties
info.FileName = Path.GetFileName(fullPath);
info.WorkingDirectory = Path.GetDirectoryName(fullPath);
// User properties
info.Domain = this.domain;
info.UserName = this.userName;
info.Password = this.password;
info.LoadUserProfile = true; // Optional, but makes sense ...
info.UseShellExecute = false; // Must be 'false' when 'UserName' is filled
// Kick Off
Process.Start(info);
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
}
}
Nice, but once you're process is running as another user (B), how do you become to original user? For things like file browsing, won't you be looking at B's My Documents, Desktop, mapped drives?
ReplyDelete