PowerShell调用UserPrincipal.ChangePassword方法时出现内存不足错误
解决UserPrincipal.ChangePassword偶发“内存不足”跨域改密问题
问题背景
在Windows 10普通用户权限机器(32GB内存,已加入Domain A)上,使用System.DirectoryServices.AccountManagement命名空间的UserPrincipal.ChangePassword方法修改Domain B用户密码时,偶发System.OutOfMemoryException(错误提示:Insufficient memory to continue the execution of the program)。
核心特征:
- $user对象每次可正常创建,仅调用
ChangePassword时出错 - 触发时机无规律,约1/5的机器会出现该问题,同一机器有时正常有时失败
- 机器实际内存远未耗尽(仅使用约1/5)
- 已尝试的无效方案:管理员权限运行脚本、用
Start-Job隔离进程、重启后立即执行、手动调用[System.GC]::Collect()回收内存 - 因权限/环境限制,无法使用
SetPassword、AD模块Cmdlet、Ctrl+Alt+Del改密、NetUserChangePassword等其他方案,必须基于Kerberos协议的当前方案解决
可行解决方案
方案1:直接操作底层DirectoryEntry并显式释放资源
绕过UserPrincipal封装层,直接调用底层DirectoryEntry的改密方法,操作完成后强制释放所有资源,避免封装层可能的内存泄漏:
Add-Type -AssemblyName System.DirectoryServices.AccountManagement Add-Type -AssemblyName System.DirectoryServices # 创建上下文 $context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( [System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainServer, $Username, $OldPassword ) $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($context, $Username) $directoryEntry = $user.GetUnderlyingObject() try { # 调用DirectoryEntry原生改密方法 $directoryEntry.Invoke("ChangePassword", $OldPassword, $NewPassword) $directoryEntry.CommitChanges() } finally { # 显式释放所有资源 $directoryEntry.Dispose() $user.Dispose() $context.Dispose() }
方案2:修改PrincipalContext的上下文选项
创建PrincipalContext时添加特定选项,强制直接绑定域控制器并禁用不必要的缓存,减少内存累积:
Add-Type -AssemblyName System.DirectoryServices.AccountManagement # 配置上下文选项:Kerberos认证+直接绑定域控制器 $contextOptions = [System.DirectoryServices.AccountManagement.ContextOptions]::Negotiate ` -bor [System.DirectoryServices.AccountManagement.ContextOptions]::Sealing ` -bor [System.DirectoryServices.AccountManagement.ContextOptions]::Signing ` -bor [System.DirectoryServices.AccountManagement.ContextOptions]::ServerBind $context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( [System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainServer, $Username, $OldPassword, $contextOptions ) $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($context, $Username) $user.ChangePassword($OldPassword, $NewPassword) # 显式释放资源 $user.Dispose() $context.Dispose()
ServerBind选项会跳过本地缓存直接绑定域控制器,避免缓存数据导致的内存异常;Negotiate/Sealing/Signing确保Kerberos认证的安全性。
方案3:拆分操作流程+分步释放对象
将创建上下文、获取用户、改密的步骤拆分,每步完成后立即释放不再使用的对象,结合强制GC回收:
Add-Type -AssemblyName System.DirectoryServices.AccountManagement $context = $null $user = $null try { # 第一步:创建上下文并获取用户 $context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( [System.DirectoryServices.AccountManagement.ContextType]::Domain, $DomainServer, $Username, $OldPassword ) $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($context, $Username) # 第二步:执行改密 $user.ChangePassword($OldPassword, $NewPassword) } finally { # 立即释放对象 if ($user) { $user.Dispose() } if ($context) { $context.Dispose() } # 强制触发GC回收未释放的资源 [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() }
额外检查项
- 确认Domain B的域控制器与当前机器的Kerberos认证通路正常,网络波动可能导致底层操作异常,伪装为内存错误
- 升级机器的.NET Framework到最新兼容版本(Windows 10推荐.NET 4.8),旧版本的
System.DirectoryServices组件可能存在内存泄漏bug
内容的提问来源于stack exchange,提问作者vnny
相关产品推荐
相关产品推荐

