Searching for Windows User SIDs in C# - c#

Context
Context first - issues I'm trying to resolve are below.
[EDIT] The application in questions is built against .NET 3.5 SP1.
One of our clients has asked as to quote how long it would take for us to improve one of our applications. This application currently provides basic user authentication in the form of username/password combinations. This client would like the ability for their employees to log-in using the details of whatever Windows User account is currently logged in at the time of running the application.
It's not a deal-breaker if I tell them no - but the client might be willing to pay the costs of development to add this feature to the application. It's worth looking into.
Based on my hunting around, it seems like storing the user login details against Domain\Username will be problematic if those details are changed. But Windows User SID's aren't supposed to change at all. I've got the impression that it would be best to record Windows Users by SID - feel free to relieve me of that if I'm wrong.
I've been having a fiddle with some Windows API calls. From within C#, grabbing the current user's SID is easy enough. I can already take any user's SID and process it using LookupAccountSid to get username and domain for display purposes. For the interested, my code for this is at the end of this post.
That's just the tip of the iceberg, however. The two issues below are completely outside my experience. Not only do I not know how to implement them - I don't even known how to find out how to implement them, or what the pitfalls are on various systems.
Any help getting myself aimed in the right direction would be very much appreciated.
Issue 1)
Getting hold of the local user at runtime is meaningless if that user hasn't been granted access to the application. We will need to add a new section to our application's 'administrator console' for adding Windows Users (or groups) and assigning within-app permissions against those users.
Something like an 'Add Windows User Login' button that will raise a pop-up window that will allow the user to search for available Windows User accounts on the network (not just the local machine) to be added to the list of available application logins.
If there's already a component in .NET or Windows that I can shanghai into doing this for me, it would make me a very happy man.
Issue 2)
I also want to know how to take a given Windows User SID and check it against a given Windows User Group (probably taken from a database). I'm not sure how to get started with this one either, though I expect it to be easier than the issue above.
For the Interested
[STAThread]
static void Main(string[] args)
{
MessageBox.Show(WindowsUserManager.GetAccountNameFromSID(WindowsIdentity.GetCurrent().User.Value));
MessageBox.Show(WindowsUserManager.GetAccountNameFromSID("S-1-5-21-57989841-842925246-1957994488-1003"));
}
public static class WindowsUserManager
{
public static string GetAccountNameFromSID(string SID)
{
try
{
StringBuilder name = new StringBuilder();
uint cchName = (uint)name.Capacity;
StringBuilder referencedDomainName = new StringBuilder();
uint cchReferencedDomainName = (uint)referencedDomainName.Capacity;
WindowsUserManager.SID_NAME_USE sidUse;
int err = (int)ESystemError.ERROR_SUCCESS;
if (!WindowsUserManager.LookupAccountSid(null, SID, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse))
{
err = Marshal.GetLastWin32Error();
if (err == (int)ESystemError.ERROR_INSUFFICIENT_BUFFER)
{
name.EnsureCapacity((int)cchName);
referencedDomainName.EnsureCapacity((int)cchReferencedDomainName);
err = WindowsUserManager.LookupAccountSid(null, SID, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse) ?
(int)ESystemError.ERROR_SUCCESS :
Marshal.GetLastWin32Error();
}
}
if (err != (int)ESystemError.ERROR_SUCCESS)
throw new ApplicationException(String.Format("Could not retrieve acount name from SID. {0}", SystemExceptionManager.GetDescription(err)));
return String.Format(#"{0}\{1}", referencedDomainName.ToString(), name.ToString());
}
catch (Exception ex)
{
if (ex is ApplicationException)
throw ex;
throw new ApplicationException("Could not retrieve acount name from SID", ex);
}
}
private enum SID_NAME_USE
{
SidTypeUser = 1,
SidTypeGroup,
SidTypeDomain,
SidTypeAlias,
SidTypeWellKnownGroup,
SidTypeDeletedAccount,
SidTypeInvalid,
SidTypeUnknown,
SidTypeComputer
}
[DllImport("advapi32.dll", EntryPoint = "GetLengthSid", CharSet = CharSet.Auto)]
private static extern int GetLengthSid(IntPtr pSID);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool LookupAccountSid(
string lpSystemName,
[MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
StringBuilder lpName,
ref uint cchName,
StringBuilder ReferencedDomainName,
ref uint cchReferencedDomainName,
out SID_NAME_USE peUse);
private static bool LookupAccountSid(
string lpSystemName,
string stringSid,
StringBuilder lpName,
ref uint cchName,
StringBuilder ReferencedDomainName,
ref uint cchReferencedDomainName,
out SID_NAME_USE peUse)
{
byte[] SID = null;
IntPtr SID_ptr = IntPtr.Zero;
try
{
WindowsUserManager.ConvertStringSidToSid(stringSid, out SID_ptr);
int err = SID_ptr == IntPtr.Zero ? Marshal.GetLastWin32Error() : (int)ESystemError.ERROR_SUCCESS;
if (SID_ptr == IntPtr.Zero ||
err != (int)ESystemError.ERROR_SUCCESS)
throw new ApplicationException(String.Format("'{0}' could not be converted to a SID byte array. {1}", stringSid, SystemExceptionManager.GetDescription(err)));
int size = (int)GetLengthSid(SID_ptr);
SID = new byte[size];
Marshal.Copy(SID_ptr, SID, 0, size);
}
catch (Exception ex)
{
if (ex is ApplicationException)
throw ex;
throw new ApplicationException(String.Format("'{0}' could not be converted to a SID byte array. {1}.", stringSid, ex.Message), ex);
}
finally
{
// Always want to release the SID_ptr (if it exists) to avoid memory leaks.
if (SID_ptr != IntPtr.Zero)
Marshal.FreeHGlobal(SID_ptr);
}
return WindowsUserManager.LookupAccountSid(lpSystemName, SID, lpName, ref cchName, ReferencedDomainName, ref cchReferencedDomainName, out peUse);
}
}

If you're using the 3.5 version of the framework, you really want to look into System.DirectoryServices.AccountManagement. I've used it before to provide lookups of AD accounts, and it's much simpler to deal with than writing your own class. It will also solve your #2 question. I don't have the code at hand, but if you need it I can always look it up.

Related

LogonUser returns 0 and GetLastError also returns 0

I'm attempting to logon to a computer remotely so that I can copy a local directory locally. I'm attempting to use the LogonUser function call found in the advapi32 DLL. When I call the function, it returns 0 (false) but when I call Marshal.GetLastWin32Error() that also returns 0, indicating that there was no error. The username and password I'm trying to use I know works because I use it to log on to the computer. I've used both domain and local accounts and both return the same result. Below is my attempt to logon to the computer.
[DllImport("advapi32.DLL", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLongonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.Dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[DllImport("kernel32.DLL", CharSet = CharSet.Auto)]
public static extern string GetLastError();
private static void CopyDirectory(string computerName, string localPath, string remotePath)
{
WindowsImpersonationContext impersonationContext = null;
IntPtr userHandle = IntPtr.Zero;
string username = #"testing";
string password = #"testing";
string fullRemotePath = string.Format("{0}\\{1}", computerName, remotePath);
try
{
bool Logon = LogonUser(username, computerName, password, 2, 0, ref userHandle);
if (!Logon)
{
Console.WriteLine("Error Logging in");
int errorCode = Marshal.GetLastWin32Error();
Console.WriteLine(string.Format("Error Code: {0}", errorCode));
Console.WriteLine(new Win32Exception(errorCode).Message);
}
else
{
WindowsIdentity user = new WindowsIdentity(userHandle);
impersonationContext = user.Impersonate();
System.IO.File.Copy(localPath, fullRemotePath, true);
}
}
catch (Exception ex)
{
int errorCode = Marshal.GetLastWin32Error();
Console.WriteLine(string.Format("Error Code: {0}", errorCode));
Console.WriteLine(ex.Message);
}
finally
{
if(impersonationContext != null)
impersonationContext.Undo();
if(userHandle != IntPtr.Zero)
CloseHandle(userHandle);
}
}
Could you please assist me in finding my flaw?
EDIT:
I found the following comment here
Even if an API supports SetLastError/GetLastError, the value returned by GetLastError is only meaningful if the API you just called actually fails. How that failure is indicated depends on the API. Except for a couple of braindead API/situation combinations, like Impersonate* with the new SeImpersonatePrivilege: the API can return a success code even if it fails due to lack of privilege, leaving you with only an identification token. Then you have to call GetLastError () to find out why the nominally-successful API call failed....
I have a feeling this is what is causing me problems. How do I get around this?
So i was over analyzing my problem and solution. Apparently i may not need to use UserLogon as This answer works in my test environment. Lets see if this works when I use it.
Edit:
Results
This all worked for me just had to create a user on my server with same credentials (username/password) and could see the other computers on the workgroup
I ended up solving this problem by using windows login credentials. so if "username/password" is your windows login then just use the same "username/password" in the "LogonUser" method. Hope it helps.

Get the PID of a Windows service

Could anyone help me to know how to get the PID of a Windows service?
I need to get the PID in order to run the following command:
Process.Start(new ProcessStartInfo
{
Filename = "cmd.exe",
CreateNoWindow = true,
UseShellExecute = false,
Arguments = string.Format("/c taskkill /pid {0} /f", pidnumber)
});
What the other answers neglect is the fact that a single process can also host multiple, autonomous services. The multiple instances of the svchost.exe process, each hosting a couple of services, is the best example.
So in general, it is absolutely unsafe to try to kill an arbitrary service by killing it's hosting process (I assume that is what you attempt to do, since you refer to taskkill.exe). You might take down several unrelated services in the process.
If you do know that the service's process only hosts the service you care about, than you can choose the strategy as suggested by #M C in his/her answer.
Alternatively, you can also use the ServiceController class to open a handle to your service and then use it (via the ServiceHandle property) to P/Invoke the QueryServiceStatusEx function to find out the Process ID you want to know.
If you need more details, you should clarify what it is that you're actually trying to achieve. It is not clear from your question.
Update Here is some code I ripped out of an existing project that should do what you want, given you have a ServiceController instance. _As said above, use with care!__
[StructLayout(LayoutKind.Sequential)]
internal sealed class SERVICE_STATUS_PROCESS
{
[MarshalAs(UnmanagedType.U4)]
public uint dwServiceType;
[MarshalAs(UnmanagedType.U4)]
public uint dwCurrentState;
[MarshalAs(UnmanagedType.U4)]
public uint dwControlsAccepted;
[MarshalAs(UnmanagedType.U4)]
public uint dwWin32ExitCode;
[MarshalAs(UnmanagedType.U4)]
public uint dwServiceSpecificExitCode;
[MarshalAs(UnmanagedType.U4)]
public uint dwCheckPoint;
[MarshalAs(UnmanagedType.U4)]
public uint dwWaitHint;
[MarshalAs(UnmanagedType.U4)]
public uint dwProcessId;
[MarshalAs(UnmanagedType.U4)]
public uint dwServiceFlags;
}
internal const int ERROR_INSUFFICIENT_BUFFER = 0x7a;
internal const int SC_STATUS_PROCESS_INFO = 0;
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool QueryServiceStatusEx(SafeHandle hService, int infoLevel, IntPtr lpBuffer, uint cbBufSize, out uint pcbBytesNeeded);
public static int GetServiceProcessId(this ServiceController sc)
{
if (sc == null)
throw new ArgumentNullException("sc");
IntPtr zero = IntPtr.Zero;
try
{
UInt32 dwBytesNeeded;
// Call once to figure the size of the output buffer.
QueryServiceStatusEx(sc.ServiceHandle, SC_STATUS_PROCESS_INFO, zero, 0, out dwBytesNeeded);
if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
{
// Allocate required buffer and call again.
zero = Marshal.AllocHGlobal((int)dwBytesNeeded);
if (QueryServiceStatusEx(sc.ServiceHandle, SC_STATUS_PROCESS_INFO, zero, dwBytesNeeded, out dwBytesNeeded))
{
var ssp = new SERVICE_STATUS_PROCESS();
Marshal.PtrToStructure(zero, ssp);
return (int)ssp.dwProcessId;
}
}
}
finally
{
if (zero != IntPtr.Zero)
{
Marshal.FreeHGlobal(zero);
}
}
return -1;
}
Assuming you know the name of the EXE the service uses and there is exactly one of them:
int procID = Process.GetProcessesByName("yourservice")[0].Id;
The method Process.GetProcessesByName("yourservice") returns an Array of Processes with your specified name, so in case you don't know how much of "yourservice.exe" runs simultaneously you might need a foreach loop.
See this answer on a similar question:
Finding out Windows service's running process name
Using a WMI query you can -
Find all services related to a single exe (a single exe can host multiple services):
select Name from Win32_Service where ProcessId = 588
Or, to answer this question, you can get the PID of the process that a service is running in:
select ProcessId from Win32_Service where Name = 'wuauserv'

Process.MainModule --> "Access is denied" [duplicate]

This question already has an answer here:
Access Denied while using System.Diagnostics.Process
(1 answer)
Closed 2 years ago.
I want to handle this differently,
ie. determine if I have access or not.
Is it possible to see if you have access to the main module or not?
foreach (Process p in Process.GetProcesses())
{
try
{
//This throws error for some processes.
if (p.MainModule.FileName.ToLower().EndsWith(ExeName, StringComparison.CurrentCultureIgnoreCase))
{
//Do some stuff
}
}
catch (Exception)
{
//Acess denied
}
}
[Flags]
private enum ProcessAccessFlags : uint
{
QueryLimitedInformation = 0x00001000
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool QueryFullProcessImageName(
[In] IntPtr hProcess,
[In] int dwFlags,
[Out] StringBuilder lpExeName,
ref int lpdwSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
int processId);
String GetProcessFilename(Process p)
{
int capacity = 2000;
StringBuilder builder = new StringBuilder(capacity);
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity))
{
return String.Empty;
}
return builder.ToString();
}
Use pinvoke with ProcessAccessFlags.QueryLimitedInformation. This will allow you to grab the filename of the process without having special admin privileges and works across x32 and x64 processes.
I see two possible causes of the exception:
It may be that your process is x86 and the process being queried is x64 or vice versa.
Every process has a so called ACL (Access control list) that describes who can interact with it, the processes you are having problems with have for security reasons an empty ACL so even as administrator you cannot mess with them. For example, there's a handfull of processes (audiodg, System, and Idle from the top of my head) that throw an exception due to the access rights.
Just use a try/catch to your loop to deal with those processes.

How can I see which shared folders my program has access to?

My program needs to read and write to folders on other machines that might be in another domain. So I used the System.Runtime.InteropServices to add shared folders. This worked fine when it was hard coded in the main menu of my windows service. But since then something went wrong and I don't know if it is a coding error or configuration error.
What is the scope of a shared folder? If a thread in my program adds a shared folder, can the entire local machine see it?
Is there a way to view what shared folders has been added? Or is there a way to see when a folder is added?
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern System.UInt32 NetUseAdd(string UncServerName, int Level, ref USE_INFO_2 Buf, out uint ParmError);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct USE_INFO_2
{
internal LPWSTR ui2_local;
internal LPWSTR ui2_remote;
internal LPWSTR ui2_password;
internal DWORD ui2_status;
internal DWORD ui2_asg_type;
internal DWORD ui2_refcount;
internal DWORD ui2_usecount;
internal LPWSTR ui2_username;
internal LPWSTR ui2_domainname;
}
private void AddSharedFolder(string name, string domain, string username, string password)
{
if (name == null || domain == null || username == null || password == null)
return;
USE_INFO_2 useInfo = new USE_INFO_2();
useInfo.ui2_remote = name;
useInfo.ui2_password = password;
useInfo.ui2_asg_type = 0; //disk drive
useInfo.ui2_usecount = 1;
useInfo.ui2_username = username;
useInfo.ui2_domainname = domain;
uint paramErrorIndex;
uint returnCode = NetUseAdd(String.Empty, 2, ref useInfo, out paramErrorIndex);
if (returnCode != 0)
{
throw new Win32Exception((int)returnCode);
}
}
Each thread in a computer runs under a specific user account. Shared folder have security settings, i.e. they are subject to ACL-based access control, so that some users may have access permission and others may not. This means that the thread in your program may be able to "see" some shared folders, while other threads in the same computer (including the interactive user using the desktop) might be unable to "see" those folders.
In summary: you should assume very little.

Impersonation and CurrentUser Registry Access

Environment: Windows XP SP3, C#, .Net 4.0
Problem:
I'm attempting to add access to an impersonated users registry hive in an impersonation class and I'm running into issues based on the type of user being impersonated (or more accurately the limitation seems to be on the impersonating user).
I was originally following an impersonation example from CodeProject which showed a call to LoadUserProfile() taking place after impersonation was started using a duplicate token generated througha call to DuplcateToken() from the original token obtained from LogonUser(). I was not able to get this example working in my environment impersonating a limited user from an administrator account (From the screen shots included in the example it appears as though it was done on a windows Vista\7 system and no details were given about the account types involved).
The call to LoadUserProfile() was throwing an error of "Access Denied". Looking at userenv.log showed the line "LoadUserProfile: failed to enable the restore privilege. error c0000022". The LoadUserProfile documentation on MSDN shows that the calling process must posses the SE_RESTORE_NAME and SE_BACKUP_NAME privileges which by default only members of the Administrators and Backup Operators groups have. (As a side note when I attempted to add these two privileges later on to the Users group i still received Access Denied but the userenv.log showed "DropClientContext: Client [number] does not have enough permission. error 5" which I couldn't find any information on)
Given that the user I was impersonating did not have these privileges I moved the call to LoadUserProfile() up to before starting the impersonation and this time it loaded without a problem and I was able to read and write to it in this test. Thinking that I had discovered my answer I created a conditional check for the type of account so that LoadUserProfile() would be called before impersonation if the current user was a member of Administrators or wait until after impersonation if the member was not a member of Administrators (in the later instance I would be relying on the impersonated user having these privileges). Unfortunately I was wrong; I had not discovered my answer. When I tested the call with the role reversed (User > Administrator) The call to LoadUserProfile() still failed again with the Access Denied error and userenv.log showed the same "LoadUserProfile: failed to enable the restore privilege. error c0000061" but with a different error number this time.
Thinking that the privileges may not be enabled by default on the tokens returned from LogonUser() and\or DuplicateToken() I added two calls to AdjustTokenPrivilege() on the current users token (taking place after impersonation) obtained from WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token.
TokenAccessLevels.AdjustPrivileges and TokenAccessLevels.Query were specified because the documentation for AdjustTokenPrivilege on MSDN specifies that they are needed on the token being adjusted (I also tried obtaining the token through a call to OpenProcessToken() using a handle retrieved from System.Diagnostics.Process.GetCurrentProcess().Handle but that failed when called from the User both inside and outside of impersonation with GetCurrentProcess() being the function that threw access denied)
AdjustTokenPrivilege() returned successfully when used with the WindowsIdentity...Token but LoadUserProfile() still resulted in Access Denied (restore privilege).
At this point I was not convinced that AdjustTokenPrivilege() was doing it's job so I set out to determine what privileges were available and what state they were in for a particular token with GetTokenInformation() which resulted in it's own little bag of fun. After learning some new things I was able to call GetTokenInformation() and print out a list of privileges and their current status but the results were somewhat inconclusive since both Restore and Backup showed an attribute of 0 before and after calling AdjustTokenPrivilege() both as the administrator and while impersonating the administrator (Strangely three other privileges changed from 2 to 1 on the token when calling AdjustTokenPrivilege() but not the ones actually being adjusted which remained at a value of 0)
I removed the call to DuplicateToken() and replaced all of the places it was being used with the token returned from LogonUser() to see if that would help though in testing the privileges on the tokens the LogonUser() and DuplicateToken() tokens were identical. When I initially wrote the impersonation class I had been using the primary token in my call to WindowsImpersonationContext.Impersonate() without any problems and figured it was worth a try.
In the code example I've provided below I am able to impersonate and access the registry of a User when run as an Administrator but not the other way around. Any help would be greatly appreciated.
Pre-Post Edit:
I've also tried using the RegOpenCurrentUser() API in place of LoadUserProfile() and had success with Administrator > self and Administrator > User impersonation but when impersonating an Administrator from either another Administrator account or a User RegOpenCurrentUser() returns a pointer to HKEY_USERS\S-1-5-18 (what ever that is) instead of the actual account hive. I'm guessing because it is not actually loaded which brings me back to square one with needing to use LoadUserProfile()
From RegOpenCurrentUser documentation (MSDN):
RegOpenCurrentUser uses the thread's token to access the appropriate key, or the default if the profile is not loaded.
Code Snippet:
// Private variables used by class
private IntPtr tokenHandle;
private PROFILEINFO pInfo;
private WindowsImpersonationContext thisUser;
private string sDomain = string.Empty;
private string sUsername = string.Empty;
private string sPassword = string.Empty;
private bool bDisposed = false;
private RegistryKey rCurrentUser = null;
private SafeRegistryHandle safeHandle = null;
//Constants used for privilege adjustment
private const string SE_RESTORE_NAME = "SeRestorePrivilege";
private const string SE_BACKUP_NAME = "SeBackupPrivilege";
private const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001;
private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
private const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004;
private const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000;
[StructLayout(LayoutKind.Sequential)]
protected struct PROFILEINFO {
public int dwSize;
public int dwFlags;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpProfilePath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpDefaultPath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpServerName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpPolicyPath;
public IntPtr hProfile;
}
protected struct TOKEN_PRIVILEGES {
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID_AND_ATTRIBUTES {
public LUID Luid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID {
public uint LowPart;
public int HighPart;
}
// Private API calls used by class
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
protected static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);
[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 Zero, IntPtr Null1, IntPtr Null2);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Start() {
tokenHandle = IntPtr.Zero; // set the pointer to nothing
if (!LogonUser(sUsername, sDomain, sPassword, 2, 0, ref tokenHandle)) {
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
} // end if !LogonUser returned false
try { //All of this is for loading the registry and is not required for impersonation to start
LUID LuidRestore = new LUID();
LUID LuidBackup = new LUID();
if(LookupPrivilegeValue(null, SE_RESTORE_NAME, ref LuidRestore) && LookupPrivilegeValue(null, SE_BACKUP_NAME, ref LuidBackup)) {
//Create the TokenPrivileges array to pass to AdjustTokenPrivileges
LUID_AND_ATTRIBUTES[] LuidAndAttributes = new LUID_AND_ATTRIBUTES[2];
LuidAndAttributes[0].Luid = LuidRestore;
LuidAndAttributes[0].Attributes = SE_PRIVILEGE_ENABLED;
LuidAndAttributes[1].Luid = LuidBackup;
LuidAndAttributes[1].Attributes = SE_PRIVILEGE_ENABLED;
TOKEN_PRIVILEGES TokenPrivileges = new TOKEN_PRIVILEGES();
TokenPrivileges.PrivilegeCount = 2;
TokenPrivileges.Privileges = LuidAndAttributes;
IntPtr procHandle = WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token;
if(AdjustTokenPrivileges(procHandle, false, ref TokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) {
pInfo = new PROFILEINFO();
pInfo.dwSize = Marshal.SizeOf(pInfo);
pInfo.lpUserName = sUsername;
pInfo.dwFlags = 1;
LoadUserProfile(tokenHandle, ref pInfo); //this is not required to take place
if(pInfo.hProfile != IntPtr.Zero) {
safeHandle = new SafeRegistryHandle(pInfo.hProfile, true);
rCurrentUser = RegistryKey.FromHandle(safeHandle);
}//end if pInfo.hProfile
}//end if AdjustTokenPrivileges
}//end if LookupPrivilegeValue 1 & 2
}catch{
//We don't really care that this didn't work but we don't want to throw any errors at this point as it would stop impersonation
}//end try
WindowsIdentity thisId = new WindowsIdentity(tokenHandle);
thisUser = thisId.Impersonate();
} // end function Start
From the LoadUserProfile docs:
Starting with Windows XP Service Pack 2 (SP2) and Windows Server 2003, the caller must be an administrator or the LocalSystem account. It is not sufficient for the caller to merely impersonate the administrator or LocalSystem account.
If your process starts as a regular user you're out of luck. You could possibly launch a new process (under the admin credentials) to load the profile.
I found that the logon type set in the call to LogonUser() can be a factor. Even when running as an administrator I couldn't get past the error unless I switched from LOGON32_LOGON_INTERACTIVE to LOGON32_LOGON_BATCH. You would need to be sure the "Log on as a batch job" group policy doesn't interfere though.

Categories