I would like to be able to retrieve the SID of a local Machine like the PSGetSID
utility from Sysinternals but using C#.
Is this possible?
Edit:
I am looking for a solution that will work for computers that may or may not be members of a Domain.
This has good helper class to use lookupaccountname win32 api call.
get machine SID (including primary domain controller)
I did not find a way to do this with native C#
You could do it via pinvoke and just get it from the Win32 API.
http://www.pinvoke.net/default.aspx/advapi32.lookupaccountname
There may also be a way to get it in "pure" .NET.
Another SO post from user ewall has a pure .NET solution with examples in both C# and PowerShell. It uses the DirectoryEntry and SecurityDescriptor objects. See:
How can I retrieve a Windows Computer's SID using WMI?
SIDs documentation:
https://learn.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers
Local user account SID form: S-1-5-21-xxxxxxxxx-xxxxxxxxx-xxxxxxxxxx-yyyy
System specific part: xxx...xxx
User account specific part: yyyy
So you just need to remove the last group of digits from a user account SID to get the system SID.
If your code is a Windows application, you can get the system SID this way:
using System.Security.Principal;
string systemSid;
using (WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent())
{
systemSid = windowsIdentity.User.Value.Substring(0, windowsIdentity.User.Value.LastIndexOf('-'));
}
If your code is a web application it usually runs in the context of an application pool identity and if your code is a Windows service it usually runs in the context of a system account (system, local service, network service). None of these identities are user accounts. So you cannot use the code above. You need to either know a user account name, or list user accounts.
If you know a user account name, you can get the system SID this way:
using System.Security.Principal;
NTAccount ntAccount = new NTAccount("MACHINE_NAME\\UserAccountName");
SecurityIdentifier sid = (SecurityIdentifier)ntAccount.Translate(typeof(SecurityIdentifier));
string systemSid = sid.Value.Substring(0, sid.Value.LastIndexOf('-'));
But you cannot assume the name of a standard user like "guest" because standard local user accounts names are localized, because this standard user account may have been deleted, and because a standard user account may be suppressed in future Windows releases.
So most of the time, from a web application or from a Windows service, you need to list user accounts by means of WMI:
// your project must reference System.Management.dll
using System.Management;
SelectQuery selectQuery = new SelectQuery("SELECT * FROM Win32_UserAccount");
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(selectQuery);
string systemSid;
foreach (ManagementObject managementObject in managementObjectSearcher.Get())
{
if (1 == (byte)managementObject["SIDType"])
{
systemSid = managementObject["SID"] as string;
break;
}
}
systemSid = systemSid.Substring(0, systemSid.Value.LastIndexOf('-'));
Win32_UserAccount class documentation:
https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
UPDATE
About 100 times faster:
using Microsoft.Win32;
RegistryKey key = null;
string sid = null;
try
{
foreach (string subKeyName in Registry.Users.GetSubKeyNames())
{
if(subKeyName.StartsWith("S-1-5-21-"))
{
sid = subKeyName.Substring(0, subKeyName.LastIndexOf('-'));
break;
}
}
}
catch (Exception ex)
{
// ...
}
finally
{
if (key != null)
key.Close();
}
Related
Good afternoon,
I am currently facing an issue where I have a list of server names, and I need to verify that a windows authenticated user has permissions to the specified server names without attempting to establish a connection to the specified server, before I present the list of servers to them. So for example:
Servers: A, B, C, D
Joe has permissions to A and D but not B and C so Joe should only see A and D in his server list.
How should I be tackling this issue? Should I be pulling from Active Directory? I know how to pull my user's identity, but where to pull the server information and find out if a user has permissions is a completely different story. Any documentation, code samples, articles, etc. are helpful.
Notes About The Work Environment
All of the servers in the list are database servers running SQL Server 2008 R2 and higher.
This is a government environment where there are heavy restrictions.
I am unable to modify Active Directory in any way.
I can query Active Directory and SQL Server all day, provided the user has permissions.
Using third party libraries and tools are not authorized.
Side Note About The Server List
This list is stored in a database, however I don't think this really helps with the permissions side. I've already solved the issue of checking permissions against the individual databases with the following SQL query:
SELECT name
FROM sys.databases
WHERE HAS_DBACCESS(name) = 1
ORDER BY name
Thank you for your help!
-Jamie
Conclusion
Essentially, I have found no possible way to query the AD groups on the remote server without attempting to establish a connection, thus the execution of all samples found will flag the users account with IA. For others with a similar issue that don't have to worry about IA dropping a hammer on users, I have included a few solutions below you can try to meet your needs (all of the following will work when implemented correctly).
Solutions
Query the server in active directory and retrieve the groups from the server, if this fails 9 times out of ten the exception message will be 'Access is denied.'. If it succeeds, proceed to pull the current user's groups and compare to the groups pulled from the server. The selected answer to this post combined with this post and/or this post will get you where you need to be with this solution.
If you have access to the server already through impersonation or other means (SQL Server Auth) then you can use the following to see if the member has any server roles assigned:
SELECT IS_SRVROLEMEMBER('public')
You could also use the Login class available in the Microsoft.SqlServer.Smo assembly using the Microsoft.SqlServer.Management.Smo namespace. However, you may or may not have issues moving the Microsoft.SqlServer.SqlClrProvider namespace from the GAC to the BIN. More information about this can be found at this StackOverflow post, and at this Microsoft Connect thread which states the following:
Client applications should not be using the assemblies from the Program Files folders unless they are from the specific SDK folders (such as "C:\Program Files (x86)\Microsoft SQL Server\130\SDK")
You could even do a basic connection test wrapped in a try catch to see if the connection will work.
using (SqlConnection conn = new SqlConnection(connString)) {
try {
conn.Open();
conn.Close();
} catch (Exception e) { Console.Write($"Connection test failed: {e.Message}"); }
}
There are a small variety of ways to achieve the overall goal, it just depends on your particular situation and how you want to approach it. For me, none of the solutions will work since in each scenario the test will attempt a connection to the server in question which will flag the user due to lack of permissions.
If you have Active Directory implemented then you should be giving users access rights to things like servers via AD groups anyways or else that creates a management nightmare. Imagine if John Smith joins your company as a sys admin, are you going to go to every server and explicitly assign him rights? Much easier to just create a server admin AD group then assign it to the server (or dictate what AD groups exists on servers and permission levels by group policy.
Why this also helps you is that when you develop applications, you can use the built in AD role provider to serve up things like this. Here is a simple example of grabbing a users groups by AD user Name
using System.DirectoryServices.AccountManagement;
public List<string> GetGroupNames(string userName)
{
List<string> result = new List<string>();
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAINHERE"))
{
using (PrincipalSearchResult<Principal> src = UserPrincipal.FindByIdentity(pc, userName).GetGroups(pc))
{
src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
}
}
return result;
}
EDIT: So if you absolutely refuse to use active directory groups to manage permissions on servers and buying a tool is out of the question, here is a class that will iterate through all of your local machine groups and give you a list of users within those groups. You could do something like have it run as a scheduled task on the server (or win service) and save it's results back to a DB so you can query or build a UI to pull and monitor this info at any time. This doesn't reach out and grab sql server permissions as you said you already have that.
public class MachinePermissions
{
string machineName { get; set; }
public List<LocalGroup> localGroups { get; set; }
public List<string> GetGroupMembers(string sGroupName)
{
List<String> myItems = new List<String>();
GroupPrincipal oGroupPrincipal = GetGroup(sGroupName);
PrincipalSearchResult<Principal> oPrincipalSearchResult = oGroupPrincipal.GetMembers();
foreach (Principal oResult in oPrincipalSearchResult)
{
myItems.Add(oResult.Name);
}
return myItems;
}
private GroupPrincipal GetGroup(string sGroupName)
{
PrincipalContext oPrincipalContext = GetPrincipalContext();
GroupPrincipal oGroupPrincipal = GroupPrincipal.FindByIdentity(oPrincipalContext, sGroupName);
return oGroupPrincipal;
}
private PrincipalContext GetPrincipalContext()
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Machine);
return oPrincipalContext;
}
public MachinePermissions()
{
machineName = Environment.MachineName;
PrincipalContext ctx = new PrincipalContext(ContextType.Machine, Environment.MachineName);
GroupPrincipal gp = new GroupPrincipal(ctx);
gp.Name = "*";
PrincipalSearcher ps = new PrincipalSearcher();
ps.QueryFilter = gp;
PrincipalSearchResult<Principal> result = ps.FindAll();
if(result.Count() > 0)
{
localGroups = new List<LocalGroup>();
foreach (Principal p in result)
{
LocalGroup g = new LocalGroup();
g.groupName = p.Name;
g.users = GetGroupMembers(g.groupName);
localGroups.Add(g);
}
}
}
}
public class LocalGroup
{
public string groupName { get; set; }
public List<String> users { get; set; }
}
You can create AD group for accessing each database, then add users to them.
In your app you can add list of groups and check if user Is member of them.
It's common practice and allow to create secure scenarios for different access right for different users. You only set permissions for group once and all members can benefit from access rights.
I'm writing a service and I am trying to get the logged in User's sid and for whatever reason it is not working. It only returns {S-1-5-18}. Yet if I create a quick console application, it works just fine.
I've tried 2 methods:
WindowsIdentity usr = WindowsIdentity.GetCurrent();
return usr.User
as well as:
UserPrincipal.Current.Sid
They both have the same affect in my service. They both only return {S-1-5-18}. Yet in a console app, they both return the full user sid.
What could be causing this?
I suppose you are running your service-process as NT AUTHORITY\SYSTEM or .\LOCALSYSTEM.
Please see KB 243330 for more detail:
SID: S-1-5-18
Name: Local System
Description: A service account that is used by the operating system.
If you want to get the SID from the desktop-session, you could eg go for (by utilizing cassia - nuget-package available) :
ITerminalServicesSession GetActiveSession()
{
var terminalServicesSession = default(ITerminalServicesSession);
var terminalServicesManager = new TerminalServicesManager();
using (var server = terminalServicesManager.GetLocalServer())
{
foreach (var session in server.GetSessions())
{
if (session.ConnectionState == ConnectionState.Active)
{
// yep, I know ... LINQ ... but this is from a plain .NET 2.0 source ...
terminalServicesSession = session;
break;
}
}
}
return terminalServicesSession;
}
The ITerminalServiceSession-instance does contain the property SessionId which should work as needed. But please, be aware that there are caveats associated with state of the session - I do not guarantee that my condition suffices, you may need to adapt the condition on ConnectionState as needed.
Those APIs will return the SID of the user executing the current process, in your case your service. S-1-5-18 is NT AUTHORITY\SYSTEM.
There can be anything from zero to many users logged on to a Windows system (for interactive use: either locally or remotely): there is no singular "logged on user".
You need to refine your requirements: why do you want to know the logged on user?
I try to authenticate users belonging to remote ActiveDirectory from my machine, which is not the same domain as the current machine or user domain. There will be no trust between my machine and remote ActiveDirectory machine.
Initial Try
I tried to authenticate a user(Input: sAMAccountName, machine's ipaddress, machine's domain username("Administrator") and machine's password(***). Able to get result that the user with 'sAMAccountName' do exist in ActiveDirectory.
My Requirement:
Imagine that already a user("qwerty") is created in ActiveDirectory
From my local machine, I will have the following information,
a. Remote ActiveDirectory ipaddress
b. Remote ActiveDirectory machine's username and password.
c. Username and password of User "qwerty"
I need to check whether User "qwerty" is present in remote ActiveDirectory's users list and validate whether the password entered is same in ActiveDirectory's Users list
Code I tried:
DirectoryEntry entry = new DirectoryEntry("LDAP://ipaddress/DC=dinesh,DC=com", name, password);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(sAMAccountName=" + name + ")";
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
isValid = true;
adsEntry.Close();
}
catch (Exception ex)
{
adsEntry.Close();
}
Do I need to create a trust between local machine and remote ActiveDirectory machine before validating Users in a remote ActiveDirectory? If yes please tell how it can be done;
After creating trust, how can I validate Users?
===========================================================================
I am able to use the solution suggested by Rainer, but with a new problem. When I create a new user via C# code from a different machine, then some properties do not set properly.
Does this need to be set compulsorily while creating user?
First some basics (independent of this question)
Authentication
The system checks if Bob is really Bob. In an Active Directory environment, this is usually done with a domain login from the workstation, Bob enters his username and password, and he gets a Kerberos ticket. Later, if he wants to access e.g. a file share on a remote fileserver, he does not need to login anymore, and can access the files without entering username/password.
Authorization
The system checks which resources Bob is allowed to access. Usually Bob is in domain groups, and a group is in the ACL (access control list) of the resource.
If there are multiple trusting domains, Bob needs to login in one domain, and can access resources in all other domains.
This is one of the main reasons using Active Directory: single sign on
Checking if user / password is valid
If you have a username and password and want to check if the password is valid, you have to do a login to the domain. There is no way of just “checking if the password is correct”.
Login means: if there is a security policy “lock account if more than 3 invalid logins”, the account will be locked out checking with wrong password, even if you “only want to check the user+password”.
Using .NET Directory Service functions
I assume here that the process is either run by a human account as a normal program, or the program is a Windows service or a scheduled task which runs under a domain “technical user” account. In this case, you do not need to provide credentials for using the AD functions. If accessing other trusting AD domains, this is also true.
If you want to login to a “foreign domain”, and there is no trust, you need to provide a username+password (as in your code).
"Manually" authenticating a user
Normally, this should not be needed. Example: ASP.NET intranet usage. The user access a web application on the current domain or trusting domain, the authentication is done “in the background” by browser and IIS (if integrated Windows authentication is on). So you never need to handle user passwords in the application.
I don’t see many use cases where a password is handled by code.
One may that your program is a helper tool for storing emergency user accounts/passwords. And you want to check periodically if these accounts are valid.
This is a simple way to check:
using System.DirectoryServices.AccountManagement;
...
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, "192.168.1.1");
bool userValid = principalContext.ValidateCredentials(name, password);
One can also use the older, raw ADSI functions:
using System.DirectoryServices;
....
bool userOk = false;
string realName = string.Empty;
using (DirectoryEntry directoryEntry =
new DirectoryEntry"LDAP://192.168.1.1/DC=ad,DC=local", name, password))
{
using (DirectorySearcher searcher = new DirectorySearcher(directoryEntry))
{
searcher.Filter = "(samaccountname=" + name + ")";
searcher.PropertiesToLoad.Add("displayname");
SearchResult adsSearchResult = searcher.FindOne();
if (adsSearchResult != null)
{
if (adsSearchResult.Properties["displayname"].Count == 1)
{
realName = (string)adsSearchResult.Properties["displayname"][0];
}
userOk = true;
}
}
}
If your real requirement is actually a validity check of user+password, you can do it in one of these ways.
However, if it is a "normal application", which just wants to check if the entered credentials are valid, you should rethink your logic. In this case, you better should rely on the single sign on capabilities of AD.
If there are further questions, please comment.
b. Remote ActiveDirectory machine's username and password.
This sounds a bit unclear. I assume you mean "a username and corresponding password in the remote domain".
There is also the concept of a machine account, which is the hostname appended with $. But that's another topic.
Creating new user
Option 1
using (DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://192.168.1.1/CN=Users,DC=ad,DC=local",
name, password))
{
using (DirectoryEntry newUser = directoryEntry.Children.Add("CN=CharlesBarker", "user"))
{
newUser.Properties["sAMAccountName"].Value = "CharlesBarker";
newUser.Properties["givenName"].Value = "Charles";
newUser.Properties["sn"].Value = "Barker";
newUser.Properties["displayName"].Value = "CharlesBarker";
newUser.Properties["userPrincipalName"].Value = "CharlesBarker";
newUser.CommitChanges();
}
}
Option 2
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "192.168.1.1",
"CN=Users,DC=ad,DC=local", name, password))
{
using (UserPrincipal userPrincipal = new UserPrincipal(principalContext))
{
userPrincipal.Name = "CharlesBarker";
userPrincipal.SamAccountName = "CharlesBarker";
userPrincipal.GivenName = "Charles";
userPrincipal.Surname = "Barker";
userPrincipal.DisplayName = "CharlesBarker";
userPrincipal.UserPrincipalName = "CharlesBarker";
userPrincipal.Save();
}
}
I leave as an exercise to you to find out which attribute goes into which User dialog entry field :-)
The Problem: When new IIS Application Pools are created and set to use the Application Pool Identity for permissions, I am unsure how to add those identities to User Groups such as Administrator or Performance Counter Users.
The Background: I'm currently writing a C#.NET library which uses Microsoft.Web.Administration in order to do the following:
Detect if IIS 7.x is installed, and if so, what components.
Install or upgrade IIS 7.x to a provided list of required components.
Create/manage one or more web sites through IIS.
Automatically create/manage one application pool per web site
The context is that this library is to be used by executable installers to provide automated deployment of a web server and web sites/services on Windows Server OSes as part of a larger software deployment. So far, all of the above has been implemented, tested, and is (mostly) functional except for the automation of some permissions that need to be performed on Application Pool / Website creation.
In my method for installing a new website, I create a new Application Pool and force it to use the Application Pool Identity:
static public void InstallSite(string name, string path, int port)
{
Site site;
var appPoolName = ApplicationPoolBaseName + name;
using (var iisManager = new ServerManager())
{
// Set up a custom application pool for any site we run.
if (!iisManager.ApplicationPools.Any(pool => pool.Name.Equals(appPoolName)))
{
iisManager.ApplicationPools.Add(appPoolName);
iisManager.ApplicationPools[appPoolName].ManagedRuntimeVersion = "v4.0";
}
iisManager.CommitChanges();
}
// ... other code here ('site' gets initialized) ...
using (var iisManager = new ServerManager())
{
// Set anonymous auth appropriately
var config = iisManager.GetWebConfiguration(site.Name);
var auth = config.GetSection("system.web/authentication");
auth.SetMetadata("mode", "Windows");
var authSection = config.GetSection("system.webServer/security/authentication/anonymousAuthentication");
authSection.SetAttributeValue("enabled", true);
authSection.SetAttributeValue("userName", string.Empty); // Forces the use of the Pool's Identity.
authSection = config.GetSection("system.webServer/security/authentication/basicAuthentication");
authSection.SetAttributeValue("enabled", false);
authSection = config.GetSection("system.webServer/security/authentication/digestAuthentication");
authSection.SetAttributeValue("enabled", false);
authSection = config.GetSection("system.webServer/security/authentication/windowsAuthentication");
authSection.SetAttributeValue("enabled", false);
iisManager.CommitChanges();
}
// ... other code here ...
}
As I understand it, this would be the best security practice, and I would then add permissions to specific web sites for anything more than minimal system access. Part of this process would be to add these Application Pool identities to User Groups, such as Administrator or Performance Monitor Users. This is where complications arise.
Now, as documented elsewhere, each Application Pool Identity exists in the format of IIS AppPool\\<pool_name> but this faux-user is not listed through the normal GUI user management controls, and does not seem to be accessible through libraries such as System.DirectoryServices.AccountManagement when following this example on SO. Also, other questions about the Application Pool Identity seem to relate to referencing it from within a child website, not from within an installation context.
So, does anyone know what the proper methods are for
a) Referencing and accessing Application Pool Identities programmatically.
b) Giving Application Pool Identities permissions by adding them User Groups.
Thanks for your well-written question. It is exactly the problem that I was trying to solve last night and it gave me enough to go on that I was able finally cobble together an answer that uses only managed code. There were three steps that I found to getting the framework to find and work with the virtual user:
using new System.Security.Principal.NTAccount(#"IIS APPPOOL\<appPoolName>") to get a handle on the account.
using .Translate(typeof (System.Security.Principal.SecurityIdentifier)) to convert it to a SID
understanding that Principal.FindByIdentity() treats that SID like it is a group, rather than a user
A final working program (Windows Server 2012 for my test) is as follows:
using System;
using System.DirectoryServices.AccountManagement;
namespace WebAdminTest
{
internal class Program
{
private static void Main(string[] args)
{
var user = new System.Security.Principal.NTAccount(#"IIS APPPOOL\10e6c294-9836-44a9-af54-207385846ebf");
var sid = user.Translate(typeof (System.Security.Principal.SecurityIdentifier));
var ctx = new PrincipalContext(ContextType.Machine);
// This is weird - the user SID resolves to a group prinicpal, but it works that way.
var appPoolIdentityGroupPrincipal = GroupPrincipal.FindByIdentity(ctx, IdentityType.Sid, sid.Value);
Console.WriteLine(appPoolIdentityGroupPrincipal.Name);
Console.WriteLine(appPoolIdentityGroupPrincipal.DisplayName);
GroupPrincipal targetGroupPrincipal = GroupPrincipal.FindByIdentity(ctx, "Performance Monitor Users");
// Making appPoolIdentity "group" a member of the "Performance Monitor Users Group"
targetGroupPrincipal.Members.Add(appPoolIdentityGroupPrincipal);
targetGroupPrincipal.Save();
Console.WriteLine("DONE!");
Console.ReadKey();
}
}
}
A solution presented itself sooner than I expected, though it's not the one I preferred. For anyone interested, there are a couple of additional options on this pinvoke page. The managed solution did not work for me, but the sample using DllImport worked. I ended up adjusting the sample to handle arbitrary groups based on mapping an enum to SID strings, and including another DllImport for:
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid);
The modified (working) function looks something like this:
static public bool AddUserToGroup(string user, UserGroup group)
{
var name = new StringBuilder(512);
var nameSize = (uint)name.Capacity;
var refDomainName = new StringBuilder(512);
var refDomainNameSize = (uint)refDomainName.Capacity;
var sid = new IntPtr();
switch (group)
{
case UserGroup.PerformanceMonitorUsers:
ConvertStringSidToSid("S-1-5-32-558", out sid);
break;
case UserGroup.Administrators:
ConvertStringSidToSid("S-1-5-32-544", out sid);
break;
// Add additional Group/cases here.
}
// Find the user and populate our local variables.
SID_NAME_USE sidType;
if (!LookupAccountSid(null, sid, name, ref nameSize,
refDomainName, ref refDomainNameSize, out sidType))
return false;
LOCALGROUP_MEMBERS_INFO_3 info;
info.Domain = user;
// Add the user to the group.
var val = NetLocalGroupAddMembers(null, name.ToString(), 3, ref info, 1);
// If the user is in the group, success!
return val.Equals(SUCCESS) || val.Equals(ERROR_MEMBER_IN_ALIAS);
}
Hopefully this will be of interest to someone else, and I would still like to know if anyone comes across a working, fully managed solution.
I'm creating a PrincipalContext object for retrieving a user's groups from our AD database (we use these then for authentication to various parts of the site).
This used to be done using forms authentication, so the code looked something like this
PrincipalContext pc =
new PrincipalContext(ContextType.Domain, "domain.com", username, password);
UserPrincipal usp =
UserPrincipal.FindByIdentity(pc, IdentityType.Guid, user.Guid.ToString());
foreach (var group in usp.GetGroups())
{
// Add group to collection
}
However, we recently switched to windows authentication, and I no longer have access to the user's password.
How can I search the AD database using the current user's credentials? I've tried using impersonation, but it throws an An operations error occurred error on the FindByIdentity line. If I forget about authentication all together I'm limited in the number of groups that are returned.
Here is a method I use, You could change it to return a collection:
public static List<string> getGrps(string userName)
{
List<string> grps = new List<string>();
try
{
var currentUser = UserPrincipal.Current;
RevertToSelf();
PrincipalSearchResult<Principal> groups = currentUser.GetGroups();
IEnumerable<string> groupNames = groups.Select(x => x.SamAccountName);
foreach (var name in groupNames)
{
grps.Add(name.ToString());
}
return grps;
}
catch (Exception ex)
{
// Logging
}
}
I assume you want the results IEnumerable, which is what I did here.
Anon's answer works for what I asked, but I also wanted to be able to search for other user's groups. The best way I've found to do this is to run the asp.net program's app pool under a service account, and then use my original code.
To do this in IIS Manager 7.5, go to the Application Pools, right click on the one your app is running under -> Advanced Settings, and change the identity from "ApplicationPoolIdentity" to a custom domain account.