5

I looking for way to convert COM object to DateTime and I saw a lot of articles about this problem (like this one -https://msdn.microsoft.com/en-us/library/ms180872(v=vs.80).aspx and this one- How to read "uSNChanged" property using C# )

However, all of those articles talking about using an object from the interface IADsLargeInteger.

I tried to look for the namespace of this interface and I just couldn't find any clue.

Community
  • 1
  • 1
neriag
  • 151
  • 1
  • 9
  • 1
    I found the answer. It is ActiveDs namespace. To find that namespace adding of refernce to C:\Windows\System32\activeds.tlb is needed. – neriag Oct 22 '15 at 07:32

3 Answers3

7

Here is a code sample including everything you need to convert from the AD type to a DateTime:

using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using ActiveDs; // Namespace added via ref to C:\Windows\System32\activeds.tlb

private DateTime? getLastLogin(DirectoryEntry de)
{
    Int64 lastLogonThisServer = new Int64();

    if (de.Properties.Contains("lastLogon"))
    {
        if (de.Properties["lastLogon"].Value != null)
        {
            try
            {
                IADsLargeInteger lgInt =
                (IADsLargeInteger) de.Properties["lastLogon"].Value;
                lastLogonThisServer = ((long)lgInt.HighPart << 32) + lgInt.LowPart;

                return DateTime.FromFileTime(lastLogonThisServer);
            }
            catch (Exception e)
            {
                return null;
            }
        }
    }
    return null;
}
Shanerk
  • 5,175
  • 2
  • 40
  • 36
  • 1
    _ActiveDs C:\Windows\System32\activeds.tlb_ is ***really required*** for working with ***ActivieDirectory in .NET*** ?. I use only `System.DirectoryServices.AccountManagement` – Kiquenet Jul 12 '16 at 07:54
  • No it is not required for working with ActiveDirectory in general. However, if you want to work with many date types in AD, such as lastLogon, you need the IADsLargeInteger type, which is included as part of the ActiveDs library. I have not seen any workaround to this but would love too! Have you converted these dates using System.DirectoryServices.AccountManagement? – Shanerk Jul 13 '16 at 02:23
  • For sample, I have seen this ***source code***: https://github.com/egil/PasswordExpireNotification/blob/master/Program.cs and anothers links in github. `maxPwdAge = TimeSpan.FromTicks((long)sr.Properties["maxPwdAge"][0]);` – Kiquenet Jul 18 '16 at 06:07
  • 1
    @Kiquenet, direct cast to long is only possible when the property is read from a `ResultPropertyValueCollection` as returned by the `DirectorySearcher`. Properties from a `DirectoryEntry` are `PropertyValueCollection` and can contain other ADSI COM objects like `IADsLargeInteger`. – browe Aug 10 '16 at 16:41
  • There is an error in a code. For right result you have to change as following: `lastLogonThisServer = ((long)lgInt.HighPart << 32) | (UInt32)lgInt.LowPart;` – Radka Apr 04 '17 at 20:14
  • 1
    @Kiquenet, not really required, you can define IAdsLargeInteger interface with ComImport attribute in your own code. See my answer below. – Mikhail Tumashenko Apr 26 '23 at 10:14
  • @Radka that is incorrect. Why would you OR the values? As you can see from Mikhail's more up-to-date answer, the correct value is obtained by adding the values together. – Shanerk Apr 27 '23 at 19:01
5

In addition to the previous answer, which shows correct code to get value from IADsLargeInteger variable , I just want to say that there's no need to add a reference to a COM Types library if you need only this interface.

To work with COM type you can define interface in your own code:

[ComImport, Guid("9068270b-0939-11d1-8be1-00c04fd8d503"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
internal interface IAdsLargeInteger
{
    long HighPart
    {
        [SuppressUnmanagedCodeSecurity] get; [SuppressUnmanagedCodeSecurity] set;
    }

    long LowPart
    {
        [SuppressUnmanagedCodeSecurity] get; [SuppressUnmanagedCodeSecurity] set;
    }
}

and use it the same way:

var largeInt = (IAdsLargeInteger)directoryEntry.Properties[propertyName].Value;
var datelong = (largeInt.HighPart << 32) + largeInt.LowPart;
var dateTime = DateTime.FromFileTimeUtc(datelong);

There's also a good article, explaining how to interpret ADSI data

Mikhail Tumashenko
  • 1,683
  • 2
  • 21
  • 28
  • This solution was spot on, the solution provided Ambrose Leung was close, seemed to be about 5 mins off. – Tim D. Apr 24 '23 at 17:28
1

You DO NOT need to reference ActiveDs.dll - instead, you can do this.

I've verified that this works in .NET Standard 2.0 and .NET 5

...
Int64 lastLogonThisServer = ConvertADSLargeIntegerToInt64(de.Properties["lastLogon"].Value);
return DateTime.FromFileTime(lastLogonThisServer);
...

public static Int64 ConvertADSLargeIntegerToInt64(object adsLargeInteger)
{
     var highPart = (Int32)adsLargeInteger.GetType().InvokeMember("HighPart", System.Reflection.BindingFlags.GetProperty, null, adsLargeInteger, null);
     var lowPart  = (Int32)adsLargeInteger.GetType().InvokeMember("LowPart",  System.Reflection.BindingFlags.GetProperty, null, adsLargeInteger, null);
     return highPart * ((Int64)UInt32.MaxValue + 1) + lowPart;
}

Credit goes to Simon Gilbee for this answer.

Ambrose Leung
  • 3,704
  • 2
  • 25
  • 36