I am trying fetch the whenChanged attribute for a user record from Active Directory using the DirectorySearcher class.
It seems the value is changed during or maybe after it is fetched because it is not the same as the value I can see in LDAP.
E.g. For my own profile the value in LDAP reads: 11/29/2022 5:10:21 Eastern Standard Time but after fetching this value through my code, it says 11/29/2022 10:10:24. Please note it is not 10:10 as per my system clock.
The obvious conclusion is that it is a time zone issue. But my system is in EST too. Also the difference in time is different for different users.
Does anyone know why this is happening?
There are a couple things going on.
As mentioned by Selvin, the value is always stored in GMT, which explains the 5-hour difference.
The value is not replicated between domain controllers, which explains the 3-second difference.
To explain the second point in more detail, let's take an example where a user's title is updated. When that update happens, the whenChanged attribute is updated on the DC where the change was made. Then when the new title value is replicated to another DC, that other DC updates the whenChanged attribute to the time when the replication happened. Because of that, the whenChanged value will be different on each DC.
The whenChanged values will usually be pretty close, but they can differ significantly. For example, when you logon, the lastLogon attribute is updated on the DC you authenticated to, along with whenChanged. However, lastLogon is not replicated, so the whenChanged value on all the other DCs will not get updated. That is, unless it's time to update the lastLogonTimestamp value, in which case that will replicate.
So if you're checking the value, make sure you're reading from the same DC. With DirectoryEntry, you can specify the DC you want to use in the LDAP path:
var user = new DirectoryEntry("LDAP://dc1.example.com/CN=someuser,OU=Users,DC=example,DC=com"
, null, null, AuthenticationTypes.ServerBind);
The use of AuthenticationTypes.ServerBind is because the documentation says:
Specifying a server name without also specifying this flag results in unnecessary network traffic.
Or you can get the DC that DirectoryEntry automatically found by using:
var dc = user.Options.GetCurrentServerName();
Related
I am using .NET Framework 4.8 by necessity. I am running into an issue where:
UserPrincipal.FindByIdentity(context, username); is resulting in a System.DirectoryServices.AccountManagement.MultipleMatchesException exception for a specific username. I can retrieve other users just fine.
There are two accounts in my Active Directory that this user utilizes, however, both of them have different UPNs, sAMAccountNames, etc.
I took a look at the IdentityType enum and the FindByIdentity overload that allows for an exact IdentityType look-up. With this information, I used each possible IdentityType and tried the searches:
None of these lines of code result in a System.DirectoryServices.AccountManagement.MultipleMatchesException exception. The Sid and Guid look-ups fail because the username field isn't in the right format, and the other look-ups either pull the user or pull null.
So what am I missing here? Does the overload that leaves out a specific IdentityType do a different look-up than anything that's possible by supplying an IdentityType?
Edit #1: As Alex pointed out, I left out that I tried IdentityType.UserPrincipalName from my screenshot. This was just a cropping mistake. That call also does not throw an exception.
When you don't specify which identifier you're using, it's going to try them all at once. The source code is available now. The code that actually builds the query and executes it is here. Specifically it's line 617 where it starts building the filter. The source for IdentityClaimToFilter is here. Then it puts each condition in an OR.
So if we ignore Guid, Sid, and DistinguishedName, since we know those won't match, this is the relevant LDAP query (assuming the username you're using is "UserName"):
(|(sAMAccountName=UserName)(userPrincipalName=UserName)(name=UserName))
Try making that query in PowerShell and see if you get multiple matches (I'm assuming the computer you run this from is joined to the domain you want to search):
$search = [adsisearcher]"(|(sAMAccountName=UserName)(userPrincipalName=UserName)(name=UserName))"
$search.FindAll()
If the sAMAccountName of one account matches the name of another account, for example, you will get multiple matches.
An external process emails messages to an Exchange mailbox. I then have an Exchange Web Services (EWS) 2.0 application to fetch those email messages for subsequent processing. The EWS application is designed to fetch messages received on the current date, eg "received:today" in AQS parlance. The messages, however, are never retrieved - none are returned to the ItemView of the FindItems method:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
// sharedMailbox is a FolderId type set to a valid well-known location not reproduced here
FindItemsResults<Item> findResults = service.FindItems(sharedMailbox,"(subject:flotsam) AND (received:today)", new ItemView(20));
On a lark, changing this date to "received:yesterday" finally caught the desired messages, but an inspection of each message's explicit received date for each of the messages was not yesterday, but today:
Console.WriteLine(item.DateTimeReceived.ToLocalTime());
Console.WriteLine(item.DateTimeReceived.ToUniversalTime());
10/24/2016 1:05:38 AM
10/24/2016 6:05:38 AM
I suspected an oddity in the translation of the constants, and opted to provide explicit dates. However, explicitly defining the receipt date in "received:MM/DD/YYYY" form (rather than 'today' or 'yesterday') exhibited the same results:
FindItemsResults<Item> findResults = service.FindItems(sharedMailbox,"(subject:flotsam) AND (received:10/23/2016)", new ItemView(20));
Although the date is yesterday, this code did fetch messages retrieved today, which is 10/24/2016 (not 10/23/2016). Code specifying today, which should fetch the desired messages, actually fetched no messages:
FindItemsResults<Item> findResults = service.FindItems(sharedMailbox,"(subject:flotsam) AND (received:10/24/2016)", new ItemView(20));
In effect, the explicit dates are behaving exactly as the 'received:yesterday' and 'received:today' querystring values, so that implies these constants are simply being mapped to the values I hard-coded.
I then suspected timezone differences, or UTC conversion differences, but our local timezone is behind UTC, meaning that specifying "received:today" should, in reality, work in fetching messages received....today.
I am at a loss to know or understand why we are seeing this single-day discrepancy in fetched messages. What aspect of message date interpretation am I handling incorrectly?
EDIT: Per suggestion in comments, I modified the FindItems call to use a SearchFilter object, using a DateTime value of Now minus one day (actually, a value 24 hours from DateTime.Now. This created a datetime value that crossed into 10/23/2016, and retrieved the messages received today. So I cannot be sure that the filter really found the messages because they were received on 10/24, or because the search date range included yesterday (which caused the other searches to work):
SearchFilter sf = new SearchFilter.IsGreaterThan(ItemSchema.DateTimeReceived, new DateTime.Now.AddDays(-1));
Taking a cue from this, I modified the filter to use a DateTime without an explicit time element:
SearchFilter sf = new SearchFilter.IsGreaterThan(ItemSchema.DateTimeReceived, new DateTime(2016,10,24));
This search filter worked, returning both messages received today. This tends to suggest some semantic hiccup or uncertainty with the 'today' and 'yesterday' keywords.
I would prefer to find an AQS-based solution, if possible, or at least find a better understanding of why the querystring values aren't working as expected.
If you are using AQS with Exchange, you are allowed to use relational operators in the search query with date values and relative date keywords.
Although odd, the following expression worked the way you expected the relative date keyword "today" to work:
>Yesterday
Excerpted from "How to: Perform an AQS search by using EWS in Exchange"
"Date value types can also be compared with relational operators like greater than or less than, or specified as a range with the range operator ... For example, received:>11/30/2013, sent:>=yesterday, and received:12/1/2013..today are all valid query strings."
I'm having an application which keeps track of appointments in Exchange using the EWS API. Using the property .ItemId is bad, because though it's unique, it's likely to change (Exchange web services: why is ItemId not constant? [continued]). For tracking items, this is a bad situation. Therefore I use the property Appointment.ICalUid.
This property also doesn't always looks the same and as far I can see, it somehow changes. I've logged some changes. At first a record is created with the following .ICalUID:
5fc22493-7212-4c44-9cd6-971c3bae28af
Then the next time I look up the records in Exchange the same item returns another .ICalUID:
040000008200E00074C5B7101A82E00800000000F01883C1D49AD101000000000000000010000000C321E8A40C6DE948836C422E2DA8610C
Why do I have at first a string returned of 36 characters and later on a string of 190 characters long? Why is this value changing?
Edit:
That the short id is created when using an Android phone connected to the Exchange Server, and the long ID is created using Outlook on Windows 10 with Outlook 2013. But it changes somehow?
This is because the RFC for iCal doesn't define the format or length of the Uid https://www.rfc-editor.org/rfc/rfc5545 just that it has to be globally unique. That means its down to implementer eg some people use a guid, some use a guid and domain etc. Exchange/Outlook uses a GOID format in particular PidLidCleanGlobalObjectId https://msdn.microsoft.com/en-us/library/office/cc839502.aspx and PidLidGlobalObjectId https://msdn.microsoft.com/en-us/library/office/cc815676.aspx (which are typically generated by the Exchange server when the appointment is created) .
So you should expect different formats and I generally recommend you use the PidLidCleanGlobalObjectId extended property rather then the icaluid property because this will always returns consistent as once its set Exchange will never change this property where the strongly typed property can be inconsistent in certain cases like you seeing. (as a general rule it should return the GOID).
Cheers
Glen
I'm not sure if my question on the face of it makes full sense, so let me try and elaborate. At the moment I try and check if a website already exists in IIS by creating a new DirectoryEntry:
DirectoryEntry IISWebsites = new DirectoryEntry(MetaBasePath);
MetaBasePath is defined earlier as:
private const string MetaBasePath = "IIS://Localhost/W3SVC";
I check IISWebsites children in a foreach loop and just wondered if this will run through the children in Id order? From what I've read this is actually stored in the DirectoryEntry 'Name' property.
The reason I ask is that if the website name entered by the user in my web setup project isn't found then I want to return the highest id so I can add 1 to it and create a new website with the name supplied by the user.
Having tested this with my IIS it does seem to return it in this order but I need to be sure.
EDIT
I've found the following on Microsoft support (http://support.microsoft.com/kb/240941):
Note that when the metabase is searched for configuration information,
it is enumerated from the bottom, or subkey, to top, or node.
This seems to imply that it does do what I think, but it's not 100% clear if it works on site Id as I'm not sure how this relates to the subkey.
The documentation does not specifically define the order as by site ID so it would not be safe to assume it will always be sorted that way (particularly as your current application eventually gets used with new versions of .NET/Windows/IIS in the future).
Most likely the number of websites is not going to be big enough that enumerating them to find the max would not be a bottleneck.
Even so, you can run a search for websites and specify the order using DirectorySearcher.Sort.
Note that in regards to your edit and how configuration information is enumerated, that does not related to sort order. The one sentence taken out of context is not as clear. Read it in context of the whole paragraph and it is clear that the enumeration behavior is related to metabase property inheritance.
I’m currently working on a project where we need to archive and trace all the modified data’s.
When a modification surrender, we have to kept theses information
Who has modified the data?
When?
And … that’s why I’m asking this question: Keep the previous
and the new value of the data.
Quickly, I have to trace every modification for every data.
Example :
I have a name field why the value “Morgan”.
When I modify this value, I have to be able to say to the user that the 6th of January, by XXX, the value changed from “Morgan” to “Robert” …
I have to find a clean and generic method to do this because a large amount of data is concerned by this behavior.
My program is in C# (.NET 4) and we are using Sql Server 2008 R2 and NHibernate for the object mapping.
Do you any ideas, experience or solution about how to do a thing like that?
I am a little confused about at what point you want to have the old vs new data available. But, this can be done within a database trigger as in the following question:
trigger-insert-old-values-values-that-was-updated
NHibernate Envers its what you want :)
You must use NHibernate 3.2+ (3.2 is the current release).
Its easy like
enversConf.Audit<Person>();
You can get info here and here
I've been in the same situation as you. I ended up doing in this way:
Save an ActivityEntry in the database containing an identity column (if you have multiple objects that change), an action-indicator (could be "User changed firstname", as a int), date field, userId and most important a parameter field.
Combining the values from the parameter field and the action-indicator I'm able to make strings like "{0} changed {1}'s firstname from {2} to {3}" where my parameter values could be "John;Joe".
I know it feels kinda wrong saving these totally loosely typed values in the database, but I believe it's the only way around, without having a copy of each table.