Active Directory userAccountControl null after otherwise valid search - c#

and thanks for reading.
I have tried many variations of the following code, and all have returned a null value for the userAccountControl property:
DirectoryEntry de = new DirectoryEntry("LDAP://{my server/domain}");
DirectorySearcher ds= new DirectorySearcher(de);
ds.Filter = "(&(objectClass=user)(objectGUID=" + queryGUID + "))";
ds.PropertiesToLoad.Add("userAccountControl");
foreach (SearchResult sr in ds.FindAll())
{
var userFlags = sr.GetDirectoryEntry().Properties["userAccountControl"].Value;
int flags = (int)userFlags;
bool enabled = (flags & 0x2) == 0x2;
Console.WriteLine("Enabled: {0}", enabled ? "true" : "false");
}
Currently it's filtering using an objectGuid I retrieve from a valid user, converted into the proper form. (Being a test program I don't care about the string concatenation...I'll fix that in production code later.) I could (and have) use(d) other search filter values, including bitwise transitive filters. I've used direct binding versus a directory search. I've written a dozen or more variations of this, and all with the same result: the query succeeds but the userFlags property itself comes back null (is not present).
Since I'm specifically asking for a user class here, I know I'm not inadvertently getting a contact class (which wouldn't have the userAccountControl property). The bitwise operations shown in the code aren't important (I know I can convert to an enum and compare that way). It crashes with a null reference exception before the bitwise operations anyway.
This is running on Windows Server 2008 R2, using .NET 4 (I know of the issue with .NET 4.5 and AD account management). The account running this has both Administrator and Enterprise Administrator privileges. Also, as an aside, I downloaded Softerra's LDAP administrator console, and it as well doesn't show this property as present.
My question is simply why is this value null? It should not be, to my limited knowledge. Did I not set AD up properly in the beginning, perhaps? The search is improperly constructed?

Is your code find user? If it s true can u try that?
//...
var results = ds.FindAll();
foreach (SearchResult sResult in results)
{
var directoryEntry = sResult.GetDirectoryEntry();
using (directoryEntry)
{
bool enabled;
const string attrib = "userAccountControl";
const int ufAccountDisable = 0x0002;
de.RefreshCache(new string[] { attrib });
var flags =(int)de.Properties[attrib].Value;
if (((flags & ufAccountDisable) == ufAccountDisable))
{
enabled = false;
}
else
{
enabled true;
}
}
}
I use this code block. I tried it, after set disable a user in active directory. It must works correctly.

Found it. It turns out that new user attributes are protected and can be accessed only by code running as administrator ("Run As"). I wasn't originally running this code using elevated privilege. Once I did, the attributes appeared. This seems to be similar behavior to querying for tombstoned objects. All I can say is "Doh!" :)

Related

c# DirectoryEntrie Propertie allowedAttributesEffective is always null

I have a very old project that worked like a charm for many years.
All of a sudden It seems that the following code snippet is only returning false because the "allowedAttributesEffective" property is null, and therefore thinking that the user has no rights to Modify an AD object
public static bool checkWritePermission(DirectoryEntry de) {
de.RefreshCache(new string[] { "allowedAttributesEffective" });
log.write("Write permission == " + (bool)(de.Properties["allowedAttributesEffective"].Value != null), 5);
// Outputs always -> "Write permission == False"
return de.Properties["allowedAttributesEffective"].Value != null;
}
The "original" codes taken years ago from here: How can I check if a user has write rights in Active Directory using C#?
In reality the user that executes this code should have modify permissions on all AD Objects (proven by the fact that I can Modify all objects in question via Active Directory Users and Computers Management Interface)
Any Ideas what the issue could be here?
I have also tried with ldapsearch on linux to see if I can read the property "allowedAttributesEffective" but I have not found a clear answer if that should even be the case as it is not returning anything on any LDAP object

Lastlogon time on DC return different value in .Net and powershell

I'm writing a little application that can audit the lastlogon time of a user on every DC in our environment.
With a little help I was able to create a code that iterate all the DC's and query the lastlogon time of a user. As I read it before this value isn't synchronizing among the DC's and this is a by design by MS.
Here is the code I use to check a user on every DC:
foreach (string DCInstance in DC_Collection)
{
try
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain,DCInstance))
{
using (UserPrincipal userPrincipal = new UserPrincipal(context))
{
userPrincipal.Name = "TestUserName";
using (PrincipalSearcher searcher = new PrincipalSearcher(userPrincipal))
{
using (PrincipalSearchResult<Principal> results = searcher.FindAll())
{
foreach (UserPrincipal FoundUser in results)
{
Console.WriteLine(FoundUser.SamAccountName + "," + FoundUser.LastLogon + "," + DCInstance);
}
}
}
}
}
}
catch (PrincipalServerDownException PSDE)
{
MessageBox.Show(PSDE.Message + ": " + DCInstance);
}
}
I know that many of the brackets are unnecessary but it is just more readable to me this way.
This is the core of the program. However I recognized that the lastlogon time of the found user is the same on each DC instance, which is quite unlikely.
To make sure of this issue I made a little PS script that do the same thing, so I can compare the results. Here is the PS script:
foreach($dc in $dcs)
{
$hostname = $dc.HostName
$user = Get-ADUser $userName -Server $hostname | Get-ADObject -Properties lastLogon
}
Again this is only the core of the script that make the actual work.
The result however are completely differs. The PS script results the the times as I expected, quite different on almost every DC (not all differs from the others but most of them). The .Net program however returns the same time from all DC instances and in addition it returns a time that do not displays in the PS script!
Now I'm quite confused. I believe the PS script is the correct one, but then I really do not know what did I missed in the .Net version.
I did some debug on the program but the found user contains only the time that the program displays, and even the filetime value different from the PS version.
I even thought on date-time conversion issue, but then the structure of the result should be the same with different values, but they aren't.
Thx in advance for any sort of help.
I found the answer! :-)
I modified the C# code to return the filetime on the assumed Last logon data, and also did a detailed ps script to list every variable of the user so I can find the relevant property returned by the c#.
Here is a PS script:
Get-ADUser TestUser | Get-ADObject -Properties *
This line returns every property that is stored in the AD about the given user.
Among others there are two value I care about:
lastLogon and lastLogonTimestamp
After this I checked the filetime version of the lastlogon property returned by the c# and it turened out, that this value is actually the same with the lastLogonTimestamp and there is no (or I did not found) way to request the exact lastlogon data.
The proper solution is to redesign the code the operate with LDAP queries instead of API calls.
Little help for that can be found here

'Access Denied' when attempting DirSync with S.DS

I am trying to set up a DirSync control. Previously I have [successfully] used the methods found in the System.DirectoryServices.Protocols, but I found that the results it returned were only partial objects - I wasn't able to have it return the homeDrive attribute from a user even if I defined it in the SearchRequest's Attributes property.
Thus, I'm attempting to set up DirSync following some of the documentation and examples using System.DirectoryServices instead. I was successful in connecting to my test server (only accessible by IP), and I was successful in targeting just one OU and searching for a user, as such:
byte[] cookie = null;
root = new DirectoryEntry(
"LDAP://[MyIPHere]/OU=test ou,DC=company,DC=com", "username", "password");
//Section A - Use this section for a regular search
DirectorySearcher src = new DirectorySearcher(root);
src.SearchScope = SearchScope.Subtree;
src.Filter = "(&(objectClass=user)(sAMAccountName=myuserhere)";
//Section B - Use this section for a DirSync
//src.DirectorySynchronization = new DirectorySynchronization(
DirectorySynchronizationOptions.IncrementalValues, cookie);
//src.Filter = "(&(objectCategory=person)(objectClass=user))";
//Execute the code whichever section is used
SearchResultCollection result = src.FindAll();
int count = result.Count;
Console.WriteLine(count.ToString());
foreach (SearchResult res in result)
{
//do things
}
However, when I try to use section B instead of section A, I get an error on the line where I'm setting the int count. (I have tried passing no parameters to the constructor of src.DirectorySynchronization as in the example, same result):
COMException was unhandled
Access is denied.
I only get the error when I try to access the properties of the result object, or try to iterate. If I set a breakpoint on the int count line and look at the result object, I see the following in the value column of the result's Count:
'result.Count' threw an exception of type
'System.Runtime.InteropServices.COMException'
I have ensured that my account has the Replicating Directory Changes security access both for the specified OU and the test domain at large (and all other security access possible). I have also tried using a separate domain admin account.
I have the same issue if I try running this on our production domain, passing no credentials when constructing the DirectoryEntry object.
Considering that I can successfully retrieve other search results, what is it about this DirectorySynchronization that is causing the access issues, and why isn't it happening when I call src.FindAll()?
(I'm open to other options, but I'd like to avoid the USNChanged tracking method for now due to it returning the full objects back, and requiring additional coding on my end.)
The base of a DirSync search must be the root of a directory partition, i.e "DC=company,DC=com" in your case.
See http://msdn.microsoft.com/en-us/library/ms677626(v=vs.85).aspx
"Base of the search" in the table.
Some more good C# example for DirSync:
http://msdn.microsoft.com/en-us/magazine/cc188700.aspx#S1
See section "Finding Your Way with DirectorySearcher".
If you want to track changes only on a OU/container, yes, you will have to use USNChange.

How can I find the SID for an app pool via Microsoft.Web.Administration?

I am working on a configuration tool that needs to set some permissions on a directory based on the identity that a specific web application runs under. The original code simply built the login name based on IIS APPPOOL\<ApppoolName> or based on the well know SID if it was a built in account.
Some similar code failed in a localized environment so I am now trying to get rid of the baked in string.
My solution was this:
public static SecurityIdentifier GetApplicationPoolSid(string name)
{
ApplicationPool pool = Manager.ApplicationPools[name];
if (pool != null)
{
var sddlForm = pool.GetAttributeValue("applicationPoolSid") as string;
if (!String.IsNullOrEmpty(sddlForm))
return new SecurityIdentifier(sddlForm);
}
return null;
}
The problem is that I found "applicationPoolSid" by poking around in the debugger and I cannot find any documentation that says that I am not taking advantage of an undocumented implementation feature that will go away in the future. (This means it won't pass code review.)
I would love to know the approved way of doing what I am looking at here. I would also be happy to know that IIS APPPOOL\<ApppoolName> is guaranteed to never be localized so then we could go back to the old way.
I can find no reference to IIS APPPOOL being localized, which would make sense since neither IIS nor APPPOOL are words in any language, so there's nothing to translate to. Although they are acronyms in English and possibly other languages, I think they are still called IIS and AppPool even in other languages.
You can, by the way, get the SID officially like this:
NTAccount f = new NTAccount(#"IIS AppPool\DefaultAppPool");
SecurityIdentifier s = (SecurityIdentifier)f.Translate(typeof(SecurityIdentifier));
String sidString = s.ToString();
You don't even really need the SID if you're going to pass it to FileSystemAccessRule(), since it has an overload that takes an IdentityReference, which NTAccount is. Just pass it the NTAccount.
This will get you the SID, but does not use the function you asked about and is written in powershell not C#. I am posting this to hopefully assist in getting on the right track regarding the generation using SHA1 rather than actually querying from Windows. Also some systems do not have some SID and user account modules installed so this can be used in those situations.
Function to generate SID based off apppool username (explanation in link at the bottom)
function Get-SID ([String]$winver, [String]$username) {
$sidPrefix = switch ($winver) {
'6.0' { 'S-1-5-80' }
default { 'S-1-5-82' }
}
$userToString = switch ( $sidPrefix ) {
'S-1-5-82' { $username.ToLower() }
default { $username.ToUpper() }
}
$userBytes = [Text.Encoding]::Unicode.GetBytes($userToString)
$sha = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
$hash = $sha.ComputeHash($userBytes)
$sid = $sidPrefix
for ( $i=0; $i -lt 5; $i++ ) {
$sid += '-' + [BitConverter]::ToUInt32($hash, $i*4)
}
return $sid
}
Example usage
Get-SID -winver "6.0" -username "DefaultAppPool"
User 6.0 for Vista or equivalent release, use 6.1/6.2 etc for after that.
Partial credit to Tomas Restrepo from winterdom.com for the powershell, although that script does not actually work because some functions are not included in the code.

How to determine if user is an Administrator, even if non-elevated

In my C# application, I need to check if the current user is a member of the Administrators group. It needs to be compatible with both Windows XP and Windows 7.
Currently, I am using the following code:
bool IsAdministrator
{
get
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
}
The problem is that this method returns false if the application is run on Windows 7 with UAC turned on as a non-elevated Administrator. How can I determine if the user is an Administrator even if the application is run as a non-elevated Administrator?
There is a Win32 API GetTokenInformation that can be used to check the current token. If the returned token is a split token, it probably is an administrator user that is running i non elevated mode.
GetTokenInformation has an output parameter tokenInformation which takes one of three values:
TokenElevationTypeDefault = 1
TokenElevationTypeFull = 2
TokenElevationTypeLimited = 3
A value of TokenElevantionTypeLimited indicates that the user is running with a split token with limited privileges. When elevated the TokenElevationTypeFull value is returned. Non-admin user has a value of TokenElevationTypeDefault.
There is a complete code example for C# at http://www.davidmoore.info/2011/06/20/how-to-check-if-the-current-user-is-an-administrator-even-if-uac-is-on/
For any VB.NET people (I know you're out there ...), here's a version that I concocted from various sources and is, I think, optimized to determine whether the current user (including elevated) is in a defined Administrators group, machine or domain, with or without UAC enabled. (Lots of credit to other posts here and elsewhere for this one!)
Firstly, it uses a static null-able Boolean to retain the Administrator status because, although the basic check is quick, the full test can take tens of seconds, so you only want to do it once - if at all if you can help it.
Secondly, it errs on the side of the basic test being incorrect/false, which is usually the case if the user is AD-administered or if the local machine has UAC enabled. So if it can decide a user is an Administrator, it will.
Thirdly, you can add or remove criteria from the AuthorizationGroups as you see fit but the ones included cover most situations.
Lastly, if anything goes wrong, you'll get False; if you want an error, you can have one, but personally I don't see the point.
Function IsAdministrator() As Boolean
Static bResult As Boolean? = Nothing
Try
If bResult Is Nothing Then
bResult = New WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)
If Not bResult Then
Dim oContext As PrincipalContext = Nothing
Try 'to get a domain context first ...
Domain.GetComputerDomain()
oContext = New PrincipalContext(ContextType.Domain)
Catch
'... if it fails, fall through to a machine context
End Try
If oContext Is Nothing Then oContext = New PrincipalContext(ContextType.Machine)
Dim oPrincipal As UserPrincipal = UserPrincipal.FindByIdentity(oContext, WindowsIdentity.GetCurrent().Name)
If oPrincipal IsNot Nothing Then
bResult = oPrincipal.GetAuthorizationGroups().Any(Function(p) _
p.Sid.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountDomainAdminsSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountAdministratorSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountEnterpriseAdminsSid))
End If
End If
End If
Catch
bResult = False
End Try
Return bResult.GetValueOrDefault(False)
End Function
I know this is old but I've found the below way to work well across all systems. I needed it to work on .net 2 and other solutions such as WinAPI and Management Objects failed on certain versions of Windows:
Launch a new process running the command net localgroup administrators and parse the output appropriately. This works with both UAC enabled and disabled and doesn't need an elevated process.
If you are an Admin, you could temporarily disable UAC from code then re-enable it. The registry key is
Key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
Value: EnableLUA
Set to: 0 to disable, 1 to enable
So you could do something like
RegistryKey myKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\Policies\\System", true);
myKey.SetValue("EnableLUA", "1", RegistryValueKind.String);
Then check your principal.. It is kindof a hack, but its worth a shot.

Categories

Resources