You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何查询AD中近一年未登录用户及排查代码空引用错误

查询近一年未登录AD用户的实现方案及现有代码错误修复

LastLogon属性范围查询的可行性

该需求完全可实现,你之前没找到对应写法是因为UserPrincipal的强类型默认查询过滤器未直接暴露LastLogon的不等值匹配入口,不存在技术层面的限制。
*注意:AD原生的LastLogon属性不会在多台域控之间同步,单域控查询返回的LastLogon值可能不准确;如果不需要100%精确的登录时间,建议改用LastLogonTimeStamp属性做筛查,该属性会按固定周期在域控间同步,查询性能和结果准确率更适合过期账号扫描场景。如果必须用精确的LastLogon值,需要遍历域内所有域控,对每个用户取多域控返回的LastLogon最大值。

现有代码空引用错误的触发原因

你当前遇到的空引用异常、拿不到准确错误信息的问题,由以下几个问题共同导致,按触发概率从高到低排序:

  • 内存过滤时未判断LastLogon空值:AD中从未登录过的内置账号、新建未启用账号的LastLogon属性为null,你写的Where(Function(u) u.LastLogon < Today.AddYears(-1))没有提前判断null,直接做值比较会直接抛出空引用。而且你是先拉取域内所有用户到内存再做过滤,只要结果里存在一个LastLogon为null的账号,整行代码就会报错。
  • 异常捕获块本身存在二次空引用问题:你在Catch块中直接调用ex.InnerException.ToString(),如果当前异常没有内部异常(即ex.InnerException为null),这行代码会抛出新的空引用异常,覆盖原始错误信息,导致你无法看到真实的报错位置和原因。
  • 未做AD连接的前置校验:如果站点运行账号没有AD读取权限、域地址填写错误,PrincipalContext初始化或FindAll()返回结果可能为null,后续直接操作这些对象就会报错。
  • 变量未显式声明:你代码中直接使用dt变量但没有在方法内声明,如果页面类没有定义对应的全局变量,也会触发对象为空的错误。
  • 逻辑bug:你在填充用户姓名时,把GivenName和Surname的拼接结果赋值给了puid字段,而非name字段,会导致最终表格里PUID列显示姓名、Name列始终为默认值"-"。

修复后的可直接部署代码

以下代码修复了上述所有问题,并且改用AD端直接过滤的方式查询,不需要拉取全量用户到内存,性能远高于原实现:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Dim domain As PrincipalContext = Nothing
    Dim dt As New DataTable()
    Dim inactiveUsers As New List(Of UserPrincipal)
    Dim oneYearThreshold As Date = Today.AddYears(-1)

    Try
        ' 初始化域连接
        domain = New PrincipalContext(ContextType.Domain, "DomainNameHere")

        ' 初始化表格结构
        dt.Columns.Add(New DataColumn("PUID", GetType(String)))
        dt.Columns.Add(New DataColumn("Name", GetType(String)))
        dt.Columns.Add(New DataColumn("Role", GetType(String)))
        dt.Columns.Add(New DataColumn("LastLogon", GetType(String)))
        dt.Columns.Add(New DataColumn("GroupCount", GetType(String)))

        ' 直接在AD端完成登录时间过滤,不返回全量用户
        Using searchResults = UserPrincipal.FindByLogonTime(domain, oneYearThreshold, MatchType.LessThanOrEquals)
            ' 过滤空对象、仅保留启用状态的用户
            inactiveUsers = searchResults.
                Cast(Of UserPrincipal)().
                Where(Function(u) u IsNot Nothing AndAlso u.Enabled = True).
                ToList()
        End Using

        ' 填充表格,所有属性访问前做null校验
        For Each u As UserPrincipal In inactiveUsers
            Dim puid As String = "-"
            Dim name As String = "-"
            Dim role As String = "-"
            Dim logon As String = "从未登录"
            Dim groupCount As String = "0"

            If u.SamAccountName IsNot Nothing Then puid = u.SamAccountName
            If u.GivenName IsNot Nothing AndAlso u.Surname IsNot Nothing Then name = $"{u.GivenName} {u.Surname}"
            If u.DisplayName IsNot Nothing Then role = u.DisplayName
            If u.LastLogon.HasValue Then logon = u.LastLogon.Value.ToString("dd MMM yyyy")

            dt.Rows.Add(puid, name, role, logon, groupCount)
            u.Dispose()
        Next

        rUsers.DataSource = dt
        rUsers.DataBind()

    Catch ex As Exception
        ' 修复异常输出的空引用问题,所有对象先判空再输出
        litError.Text = String.Empty
        If domain IsNot Nothing Then
            litError.Text &= $"Connected Domain: {domain.Name}<br /><br />"
        End If
        litError.Text &= $"Error Source: {ex.Source}<br />"
        litError.Text &= $"Error Message: {ex.Message}<br /><br />"
        If ex.InnerException IsNot Nothing Then
            litError.Text &= $"Inner Exception: {ex.InnerException.Message}<br />{ex.InnerException.StackTrace}<br /><br />"
        End If
        litError.Text &= $"Full Stack Trace:<br />{ex.StackTrace}"
    Finally
        ' 手动释放AD连接资源,避免连接泄漏
        If domain IsNot Nothing Then domain.Dispose()
    End Try
End Sub

部署前注意事项

  • 确认站点运行的应用程序池所用账号,对目标域拥有用户信息的读取权限,否则会出现连接被拒绝的错误。
  • 如果域内有多台域控,且需要100%准确的最后登录时间,需要补充遍历所有域控查询LastLogon取最大值的逻辑,避免漏判或误判。
  • 所有AD相关操作对象都实现了IDisposable接口,用完必须及时释放,否则长时间运行会占满AD连接池导致服务不可用。

内容的提问来源于stack exchange,提问作者P S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:48:22