C# (CSharp, C-Sharp) Windows Active Directory last logon

January 7, 2017

Hi guys

Please note, many threads were referred in order to compile the script attached below & all I did was re-arranging in order for better reading/formatting

(And I insure that the script works under following scenarios:

  1. You are a domain administrator
  2. You are administrating Windows 2003 onwards Windows domain
  3. Your puter is connected to the domain network ;)

)

Referenced websites/threads

  1. http://stackoverflow.com/questions/15775264/searching-for-lastlogon-attribute-of-user-in-multiple-domain-servers
  2. https://www.codeproject.com/kb/security/lastlogonacrossallwindows.aspx#_comments
  3. http://codebeautify.org/csharpviewer (for formatting the C# code)

How to test the code

Start Visual Studio (I am using 2013 Professional edition, you can use any of the community editions to test the scripts)

Create a new C# Console Application and name it llogon (else you need to change the namespace name “llogon” according to the name you have chosen for your new project.

Add the following references to your project

  1. “Framework -> System.DirectoryServices”
  2. “Browse and add -> C:\Windows\System32\activeds.tlb”

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//Added by Rajesh

//using System.Management;
//using System.Data;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
//http://stackoverflow.com/questions/33274162/the-namespace-of-iadslargeinteger
//ActiveDS namespace
using ActiveDs; // Namespace added via ref to C:\Windows\System32\activeds.tlb
//https://www.codeproject.com/kb/security/lastlogonacrossallwindows.aspx

namespace llogon
{
    class Program
    {
        static void Main(string[] args)
        {
            // Get the root entry
            DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE");
            string configurationNamingContext =
             (string)rootDSE.Properties["configurationNamingContext"].Value;
            string defaultNamingContext =
             (string)rootDSE.Properties["defaultNamingContext"].Value;
            Dictionary<string, Int64> lastLogons = new Dictionary<string, Int64>();
            // Get all the domain controllers
            DirectoryEntry deConfig = new DirectoryEntry("LDAP://" + configurationNamingContext);
            DirectorySearcher dsConfig = new DirectorySearcher(deConfig);
            dsConfig.Filter = "(objectClass=nTDSDSA)";
            foreach (SearchResult srDomains in dsConfig.FindAll())
            {
                DirectoryEntry deDomain = srDomains.GetDirectoryEntry();
                if (deDomain != null)
                {
                    string dnsHostName =
                     deDomain.Parent.Properties["DNSHostName"].Value.ToString();
                    // Get all the users for that domain
                                   DirectoryEntry deUsers =
                     new DirectoryEntry("LDAP://" + dnsHostName + "/" + defaultNamingContext);
                    DirectorySearcher dsUsers = new DirectorySearcher(deUsers);
                    dsUsers.Filter = "(&(objectCategory=person)(objectClass=user))";
                    foreach (SearchResult srUsers in dsUsers.FindAll())
                    {
                        DirectoryEntry deUser = srUsers.GetDirectoryEntry();
                        if (deUser != null)
                        {
                            // Get the distinguishedName and lastLogon for each user
                            // Save the most recent logon for each user in a Dictionary object
                            string distinguishedName = deUser.Properties["distinguishedName"].Value.ToString();
                            Int64 lastLogonThisServer = new Int64();
                            if (deUser.Properties["lastLogon"].Value != null)
                            {
                                IADsLargeInteger lgInt =
                                 (IADsLargeInteger)deUser.Properties["lastLogon"].Value;
                                lastLogonThisServer = ((long)lgInt.HighPart << 32) + lgInt.LowPart;
                            }

                            // Save the most recent logon for each user in a Dictionary object
                            if (lastLogons.ContainsKey(distinguishedName))
                            {
                                if (lastLogons[distinguishedName] < lastLogonThisServer)
                                {
                                    lastLogons[distinguishedName] = lastLogonThisServer;
                                }
                            }
                            else
                            {
                                lastLogons.Add(distinguishedName, lastLogonThisServer);
                            }
                            string readableLastLogon = DateTime.FromFileTime(lastLogonThisServer).ToString();
                            Console.WriteLine("User: " + distinguishedName + "Last logon: " +readableLastLogon);
                        }
                    }
                    Console.ReadLine();
                }
            }

        }
    }
}

Try the code & if you are stuck somewhere, do let me through the comments. I am working on a WPF C# project for a simple Active Directory Reporter / Asset Management System using WMI. Stay tuned & I will soon post the entire solution here :)

regards,

rajesh

 


VB.Net | Active Directory | Windows Form Application for listing users & details in a grid view

January 15, 2015

Have you read our previous post about finding last logon date time value for a domain member computer or user yet? If not, please read about it here

Now, we are going to provide you a decent (Warning: Amateurish code blocks possible) Windows forms based application that could list all your domain users and few of their details including last logon date time values.

run

Above image demonstrates the run time interface of the application. Application has a search textbox, that could accept values before and after the user details are populated.

Before the population, the users will be limited to matching string, after population, the list could be refined against the combination of keystrokes

Mandatory References as seen with the below image

ref

You can download the solution from here

regards,

rajesh


VB.net | Active Directory | Get last logon for computer/user account

January 15, 2015

Ever wondered how to fetch the last logon details for a domain member computer or user? Using .Net Microsoft has made it pretty easier for the developers to populate the active directory attributes to desired data repositories, however getting the last logon date time value still remains a complex stuff (for beginners like us), especially when your domain consist of multiple domain controllers and they are spread across different geographical areas and subnets :)

After dwelling for long a while, I came across a C# code snippet @ http://www.codeproject.com/Articles/19181/Find-LastLogon-Across-All-Windows-Domain-Controlle that was mostly built against http://www.rlmueller.net/Last%20Logon.htm

I had to copy the codes those were split into multiple blocks for proper explanations, re-arrange and then using the online C# to VB.Net converters, convert to readable VB.Net coding. Again, there were few mismatches and I was able to figure them out through stackoverflow & tech forums posts.

Without going on with what and how I did it, here comes the complete coding. Please note, this is a console application solution, and you should add the references as seen with the below image in order to successfully call the methods and types

Ref

Imports System.DirectoryServices
Imports ActiveDs


Module Module1

    ''' &lt;summary&gt;
    ''' You should able to easily convert this console application to windows form application
    ''' with least efforts
    ''' Original code was posted with http://www.codeproject.com/Articles/19181/Find-LastLogon-Across-All-Windows-Domain-Controlle
    ''' using C#, I converted most of the C# Codes using online converter(s)
    ''' and ammended at few places, as the code block provided with the project were not fetching decent results
    ''' Rajesh Thampi / 15-Jan-2015 | w7bugs at gmail dot com | windows7bugs.wordpress.com
    ''' &lt;/summary&gt;
    ''' &lt;remarks&gt;&lt;/remarks&gt;
    Sub Main()
        'Get the root of the directory data tree on a directory server.
        Dim rootDse As New DirectoryEntry(&quot;LDAP://rootDSE&quot;)
        'Dictionary object to hold the records retrived by the search
        Dim lastLogons As Dictionary(Of String, Int64) = New Dictionary(Of String, Int64)
        'Local variable for holding formatted last logon values in datetime format
        Dim llogon As DateTime = Nothing


        'for User last logon
        'Dim TargetUsername As String = &quot;george&quot; ' -&gt; Pass sAMAccountName 
        'Dim objType As Integer = 805306368 '-&gt; 805306368  User | 805306369 Computer

        'for Computer
        Dim TargetUsername As String = &quot;JOSE-KSP$&quot; ' -&gt; Pass sAMAccountName 
        Dim objType As Integer = 805306369 '-&gt; 805306368  User | 805306369 Computer


        'Loop through all available domain controllers
        For Each dsDC As DirectoryServices.ActiveDirectory.DomainController In _
         DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain.DomainControllers
            'Print the individual domain controller name
            'Debug.Write(dsDC.Name &amp; vbCrLf) ' Uncomment for print
            'Get the Entry details for domain controller
            Dim dirEntry As DirectoryEntry = New DirectoryEntry(&quot;LDAP://&quot; + dsDC.Name + &quot;/&quot; + DirectCast(rootDse.Properties(&quot;defaultNamingContext&quot;).Value, String))
            'Define searcher for the objects of interest
            Dim dirObjects As DirectorySearcher = New DirectorySearcher(dirEntry)
            'Define a large integer to hold the COM object translated value
            'ActiveDS COM object is a MUST reference, yet to find another way to deal with highpart, lowpart values obtained from &quot;lastlogon&quot;
            Dim lastLogonThisServer As Int64 = New Int64()
            'Set up the filter for object to be returned through findall method
            dirObjects.Filter = &quot;(&amp;(sAMAccountType=&quot; &amp; objType &amp; &quot;)(sAMAccountName=&quot; &amp; TargetUsername &amp; &quot;))&quot;
            ' Loop through the records found
            For Each objRecords In dirObjects.FindAll()
                'Get the directory entry, this will return all the attributes associated with the object (Computer/User)
                Dim dirObj As DirectoryEntry = objRecords.GetDirectoryEntry()
                If Not dirObj Is Nothing Then
                    ' Get the distinguishedName and lastLogon for each user


                    Dim distinguishedName As String = dirObj.Properties(&quot;distinguishedName&quot;).Value.ToString()

                    Try

                        If Not dirObj.Properties(&quot;lastLogon&quot;).Value Is Nothing Then
                            Dim lgIntas As ActiveDs.LargeInteger = dirObj.Properties(&quot;lastLogon&quot;).Value
                            Dim lngHigh As Long = lgIntas.HighPart
                            Dim lngLow As Long = lgIntas.LowPart
                            lastLogonThisServer = (lngHigh * (2 ^ 32) - lngLow)


                        End If

                    Catch ex As Exception
                        Debug.Write(ex.Message)
                    End Try
                    'Different date time formats you can play around with
                    'Dim format As String = &quot;MMM ddd d HH:mm yyyy&quot;  -&gt; Jan Thu 15 08:11 2015
                    'Dim format As String = &quot;dd/MMM/yyyy HH:mm tt&quot; -&gt; 15/Jan/2015 08:36 AM
                    'Debug.Write(distinguishedName &amp; &quot;;&quot; _
                    '&amp; DateTime.FromFileTime(lastLogonThisServer).ToString(format, Globalization.CultureInfo.InvariantCulture) &amp; vbCrLf)

                    'Save the most recent logon for each user in a Dictionary object
                    'How it works
                    'lastLogons dictionary object has two parts, key and corresponding value
                    'Prior adding a new record, using ContainsKey call we can check the array for existing keys
                    'With this example, the key is &quot;distinguishedName&quot;
                    'If the distinguishedName as key found

                    If lastLogons.ContainsKey(distinguishedName) Then
                        'We compare the latest fetched logon date integer value against the key value of the dictionary object
                        If lastLogons(distinguishedName) &lt; lastLogonThisServer Then
                            'If the existing value is smaller than the new value &quot;lastLogonThisServer&quot; holds
                            lastLogons(distinguishedName) = lastLogonThisServer
                            'We will update the existing key value with recent lastLogonThisServer value
                        End If
                    Else
                        'We will add a new entry to the dictionary object
                        lastLogons.Add(distinguishedName, lastLogonThisServer)
                    End If
                End If
            Next
        Next
        'Now we will loop through the dictionary object and fetch the details
        'For a single user/computer the dictionary object will not have more than one entry
        For Each kvp As KeyValuePair(Of String, Int64) In lastLogons
            Dim v1 As String = kvp.Key
            Dim v2 As DateTime = DateTime.FromFileTime(kvp.Value).ToString()
            ' llogon = v2
            Dim format As String = &quot;dd/MMM/yyyy HH:mm tt&quot;
            Debug.Write(&quot;Distinguished Name :&quot; &amp; v1 &amp; &quot;;   Last logged :&quot; &amp; v2.ToString(format) &amp; vbCrLf)

        Next


    End Sub

End Module

You may download the solution from https://drive.google.com/file/d/0B-3iVeOMTCbWWFliTGN1NmNxMjQ/view?usp=sharing

regards,

rajesh