Search code examples
windowswmiwmi-query

Wrong LastLogon returned from Win32_NetworkLoginProfile


Using WMI Explorer (or any other tool), the LastLogon timestamp for my user is showing an outdated value instead of the current date (since I'm currently using this PC):

SELECT * FROM Win32_NetworkLoginProfile
--
LastLogon = 20150212180405.000000+120

At the same time, other domain users are listed with LastLogon as the current date, so this is an issue for my user only.

On the other hand, NetUsers is reporting the current date, as expected:

DOMAIN\user    user name    2015/03/10 10:14

What is the cause of the WMI wrong result?


Environment: Win 7 x64, domain user added in the local admins group.


Solution

  • I had the pleasure of receiving an answer from an Optimum X developer of NetUsers.exe, extremely professional and very informative.

    They made the program in c++ and the most reliable way to track login times was reading the LastWriteTime on the registry key of each profile located under "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"

    After I personally tried every class with WMI, they all failed me, and so I resorted to pInvoke for reading the hidden registry property "LastWriteTime" using C#

    Here's the main function you'd call on a HKLM RegistryKey:

        private static DateTime GetHKLMRegistryKeyLastWriteTime(RegistryKey key, string RemoteComputer)
        {
            DateTime LastWriteTime = DateTime.MinValue;
            //set RegSAM access
            RegSAM desiredSAM = RegSAM.Read;
            //set key to same navigation (win32 vs win64)
            if (key.View == RegistryView.Registry32)
            {
                desiredSAM |= RegSAM.WOW64_32Key;
            }
            else if(key.View == RegistryView.Registry64)
            {
                desiredSAM |= RegSAM.WOW64_64Key;
            }
    
            //Get Registry Hive Key on RemoteComputer.
            UIntPtr computerRegHive = ConnectToRegistryHive(RemoteComputer, HKEY_LOCAL_MACHINE);
    
            if(computerRegHive != UIntPtr.Zero)
            {
                string keyPath = key.Name;
                int rootSeperatorIndex = keyPath.IndexOf(@"\");
                if (rootSeperatorIndex != -1)
                {
                    keyPath = keyPath.Substring(rootSeperatorIndex + 1, keyPath.Length - (rootSeperatorIndex + 1));
                }
    
                UIntPtr computerRegKey = OpenRegistrySubKey(computerRegHive, keyPath, desiredSAM);
                //We no longer need computerRegHive, close!
                RegCloseKey(computerRegHive);
                if(computerRegKey != UIntPtr.Zero)
                {
                    LastWriteTime = GetRegistryKeyLastWriteTime(computerRegKey);
                    //We no longer need computerRegKey, close!
                    RegCloseKey(computerRegKey);
                }
            }
            return LastWriteTime;
        }
    

    And Here's the stuff you need to make it work:

    public static uint HKEY_LOCAL_MACHINE = 0x80000002u;
    
    [DllImport("advapi32.dll")]
    private static extern int RegConnectRegistry(string lpmachineName, uint hKey, out UIntPtr phKResult);
    
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
    private static extern int RegOpenKeyEx(
            UIntPtr hKey,
            string subKey,
            int ulOptions, //Set to 0
            RegSAM samDesired, //Desired Access (win32/win64 & Read or ReadWrite)
            out UIntPtr hkResult);
    
    [DllImport("advapi32.dll")]
    private static extern int RegQueryInfoKey(
            UIntPtr hKey,
            StringBuilder lpClass,
            IntPtr lpcbClass,
            IntPtr lpReserved,
            IntPtr lpcSubKeys,
            IntPtr lpcbMaxSubKeyLen,
            IntPtr lpcbMaxClassLen,
            IntPtr lpcValues,
            IntPtr lpcbMaxValueNameLen,
            IntPtr lpcbMaxValueLen,
            IntPtr lpcbSecurityDescriptor,
            [Out][Optional]out FILETIME lpftLastWriteTime
        );
    
    [DllImport("advapi32.dll")]
    private static extern int RegCloseKey(UIntPtr hKey);
    
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool FileTimeToSystemTime([In] ref FILETIME lpFileTime, out SYSTEMTIME lpSystemTime);
    
    [Flags]
    public enum RegSAM
    {
        QueryValue = 0x0001,
        SetValue = 0x0002,
        CreateSubKey = 0x0004,
        EnumerateSubKeys = 0x0008,
        Notify = 0x0010,
        CreateLink = 0x0020,
        WOW64_32Key = 0x0200,
        WOW64_64Key = 0x0100,
        WOW64_Res = 0x0300,
        Read = 0x00020019,
        Write = 0x00020006,
        Execute = 0x00020019,
        AllAccess = 0x000f003f
    }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct FILETIME
        {
            public uint LowPart;
            public uint HighPart;
        };
    
        [StructLayout(LayoutKind.Sequential, Pack = 2)]
        public struct SYSTEMTIME
        {
            public ushort Year;
            public ushort Month;
            public ushort DayOfWeek;
            public ushort Day;
            public ushort Hour;
            public ushort Minute;
            public ushort Second;
            public ushort Milliseconds;
    
            public SYSTEMTIME(DateTime dt)
            {
                dt = dt.ToUniversalTime();
                Year = Convert.ToUInt16(dt.Year);
                Month = Convert.ToUInt16(dt.Month);
                DayOfWeek = Convert.ToUInt16(dt.DayOfWeek);
                Day = Convert.ToUInt16(dt.Day);
                Hour = Convert.ToUInt16(dt.Hour);
                Minute = Convert.ToUInt16(dt.Minute);
                Second = Convert.ToUInt16(dt.Second);
                Milliseconds = Convert.ToUInt16(dt.Millisecond);
            }
    
            public SYSTEMTIME(ushort year, ushort month, ushort day, ushort hour = 0, ushort minute = 0, ushort second = 0, ushort millisecond = 0)
            {
                Year = year;
                Month = month;
                Day = day;
                Hour = hour;
                Minute = minute;
                Second = second;
                Milliseconds = millisecond;
                DayOfWeek = 0;
            }
    
            public static implicit operator DateTime(SYSTEMTIME st)
            {
                if (st.Year == 0 || st == MinValue)
                    return DateTime.MinValue;
                if (st == MaxValue)
                    return DateTime.MaxValue;
                return new DateTime(st.Year, st.Month, st.Day, st.Hour, st.Minute, st.Second, st.Milliseconds, DateTimeKind.Utc);
            }
    
            public static bool operator ==(SYSTEMTIME s1, SYSTEMTIME s2)
            {
                return (s1.Year == s2.Year && s1.Month == s2.Month && s1.Day == s2.Day && s1.Hour == s2.Hour && s1.Minute == s2.Minute && s1.Second == s2.Second && s1.Milliseconds == s2.Milliseconds);
            }
    
            public static bool operator !=(SYSTEMTIME s1, SYSTEMTIME s2)
            {
                return !(s1 == s2);
            }
    
            public static readonly SYSTEMTIME MinValue, MaxValue;
    
            static SYSTEMTIME()
            {
                MinValue = new SYSTEMTIME(1601, 1, 1);
                MaxValue = new SYSTEMTIME(30827, 12, 31, 23, 59, 59, 999);
            }
    
            public override bool Equals(object obj)
            {
                if (obj is SYSTEMTIME)
                    return ((SYSTEMTIME)obj) == this;
                return base.Equals(obj);
            }
    
            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }
    
    
        /// <summary>
        /// When a handle returned is no longer needed, it should be closed by calling RegCloseKey.
        /// </summary>
        private static UIntPtr ConnectToRegistryHive(string RemoteComputer, uint hKey)
        {
            UIntPtr computerRegHive = UIntPtr.Zero;
            RegConnectRegistry(@"\\" + RemoteComputer, hKey, out computerRegHive);
            return computerRegHive;
        }
    
        /// <summary>
        /// When a handle returned is no longer needed, it should be closed by calling RegCloseKey.
        /// </summary>
        private static UIntPtr OpenRegistrySubKey(UIntPtr CurrentHKey, string SubKeyName, RegSAM desiredSAM)
        {
            UIntPtr hRegKey = UIntPtr.Zero;
            RegOpenKeyEx(CurrentHKey, SubKeyName, 0, desiredSAM, out hRegKey);
            return hRegKey;
        }
    
        private static DateTime GetRegistryKeyLastWriteTime(UIntPtr hKey)
        {
            FILETIME ft = new FILETIME();
    
            int ret = RegQueryInfoKey(hKey, null, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 
                                      IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ft);
            if(ret == 0)
            {
                SYSTEMTIME st = new SYSTEMTIME(DateTime.MinValue);
                FileTimeToSystemTime(ref ft, out st);
                //Thanks to a highly developed SYSTEMTIME struct which has a DateTime implicit operator .... it's like magic!
                DateTime LastWriteTime = st;
                return LastWriteTime.ToLocalTime();
            }
            return DateTime.MinValue;
        }
    

    As for why the Win32_NetworkLoginProfile fails, the machine is pulling the datestamp on accounts from a Domain Controller, so it's complete inaccurate as to actual local logins on the machine.