PowerShell Active Directory导入脚本在PS 3.0或更高版本上失败

时间:2018-11-26 15:53:39

标签: powershell active-directory

我对PowerShell不太了解,但是已经从不再可以寻求帮助的人那里继承了脚本。此脚本导入与用户和计算机相关的AD组信息和成员身份。在装有PS 2.0的计算机上运行时,它可以正常工作,但如果在PS 3.0或更高版本上执行,则崩溃。

我无法弄清楚需要修改什么,但是似乎错误开始出现在“计算机”成员资格导入步骤中,并且有数百个错误全部显示:

处理计算机时命令失败:,引发了'System.OutOfMemoryException'类型的异常

然后在某个时候,脚本似乎停止了,甚至没有到达第三步/函数。

有什么建议吗?

[Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices") | Out-Null

$DBServer = "DBSERVER"
$DBName = "DBNAME"
$TableUsers = "[$DBName].[dbo].[AD_GroupToClient]"
$TableComps = "[$DBName].[dbo].[AD_GroupToDevice]"
$TableGroups = "[$DBName].[dbo].[AD_Group_Info]"
$sqldateformat = "yyyy/MM/dd HH:mm:ss:fff"

[system.Data.SqlClient.SqlConnection]$global:SqlConnection = $null

function Get-ScriptPath { $Invocation = (Get-Variable MyInvocation -Scope 1).Value; Split-Path $Invocation.MyCommand.Path }
$ScriptPath = Get-ScriptPath
$Logfile = "$ScriptPath\OutLog.log"

function Write-Logfile {
param($logtext)
[string](Get-Date -format $sqldateformat) + "`t$logtext" | Out-File $Logfile -Encoding ascii -Append
}

function Open-Database {
$global:SqlConnection = New-Object system.Data.SqlClient.SqlConnection 
try {
    $global:SqlConnection.ConnectionString = "Server=$DBServer;Database=$DBName;Integrated Security=True"
    $global:SqlConnection.Open() | Out-Null
    Write-Logfile "OK`tDatabase opened"
} catch {
    Write-Host "Error Opening SQL Database`t$($_.Exception.Message)"
    Write-Logfile "Error`tDatabase open failed, $($_.exception.message)"
    exit
}
}

function Close-Database {
$global:SqlConnection.Close()
Write-Logfile "OK`tDatabase closed"
}

function Esc-Quote {
param($str)
if ($str) { $str.Replace("'","''") }
}

 function Run-DBCommand {
 param($SqlCommands, [switch]$getnumrows)
 if ($SqlCommands.Count -ge 1) {
    $SqlCommandText = [string]::Join(";", $SqlCommands)
    try {
        $SqlCmd = New-Object Data.SqlClient.SqlCommand($SqlCommandText, $SqlConnection)
        $returnvalue = $SqlCmd.ExecuteNonQuery()
        if ($getnumrows) { return $returnvalue }
    } catch {
        Write-Logfile "Error`tSQL Command failed, $($_.exception.message)"
    }
  }
}

function Run-GroupMemberExport {
param($exportmode)
 switch ($exportmode) {
    "users" {
        $dom = [ADSI]"LDAP://OU=Clients123,DC=test1,DC=test2,DC=test3"
        $query = "(&(objectClass=user)(objectCategory=person)(samaccountname=*))"
        $table = $TableUsers
        $namecolumn = "AD_Group_Member_Name"
        $attribs = @("samaccountname")
    }
    "computers" {
        $dom = [ADSI]"LDAP://DC=test1,DC=test2,DC=test3"
        $query = "(&(objectClass=computer)(samaccountname=*))"
        $table = $TableComps
        $namecolumn = "AD_Group_Member_Device"
        $attribs = @("samaccountname", "whencreated")
    }
}
$starttime = (Get-Date).ToUniversalTime().ToString($sqldateformat)
$srch = New-Object DirectoryServices.DirectorySearcher($dom, $query, $attribs)
$srch.PageSize = 1000
$srch.Sort = New-Object DirectoryServices.SortOption("sAMAccountName", [DirectoryServices.SortDirection]::Ascending)
$results = $srch.FindAll()

$count = 0
$numaccounts = $results.Count
foreach ($res in $results) {
    try {
        $objAccount = $res.GetDirectoryEntry()
        $samaccountname = $objAccount.properties["samaccountname"][0]
        $whencreated = ""
        if ($exportmode -eq "computers") { $whencreated = Get-Date ([datetime]$objAccount.properties["whencreated"][0]) -Format $sqldateformat }
        $count++
        Write-Progress "Querying accounts" $samaccountname -PercentComplete ($count * 100.0 / $numaccounts)
        $objAccount.psbase.RefreshCache("tokenGroups")
        $SIDs = $objAccount.psbase.Properties.Item("tokenGroups")
        $groups = @()
        ForEach ($Value In $SIDs) {
            $SID = New-Object System.Security.Principal.SecurityIdentifier $Value, 0
            try {
                $Group = $SID.Translate([System.Security.Principal.NTAccount]).Value
            } catch {
                $Group = $SID.Translate([System.Security.Principal.SecurityIdentifier]).Value
            }
            if ($groups -notcontains $Group -and $Group.Split("\")[1] -ne $samaccountname) { $groups += $Group }
        }
        Run-DBCommand @("DELETE FROM $table WHERE [$namecolumn] = '$(Esc-Quote $samaccountname)'")
        $sqlcommands = @()
        $currenttime = (Get-Date).ToUniversalTime().ToString($sqldateformat)
        if ($groups) {
            $groups | sort | foreach {
                if ($exportmode -eq "users") {
                    $sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $_)', '$currenttime')"
                } else {
                    $sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update], [Record_Created]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $_)',                             '$currenttime', '$whencreated')"
                }
                if ($sqlcommands.count -ge 50) { Run-DBCommand $sqlcommands; $sqlcommands = @() }
            }
        } else {
            if ($exportmode -eq "users") {
                $sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update]) VALUES ('$(Esc-Quote $samaccountname)', 'ERROR: Unable to retrieve groups',                         '$currenttime')"
            } else {
                $sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update], [Record_Created]) VALUES ('$(Esc-Quote $samaccountname)', 'ERROR: Unable to retrieve                       groups', '$currenttime', '$whencreated')"
            }
        }
        Run-DBCommand $sqlcommands
    } catch {
        Write-Logfile "Error`tCommand failed while processing $exportmode`: $($objAccount.name), $($_.exception.message)"
    }
 }
 Write-Progress " " " " -Completed
 if ($count -eq $numaccounts) {
    $numdeleted = Run-DBCommand @("DELETE FROM $table WHERE [Last_Update] < '$starttime' OR [Last_Update] IS NULL") -getnumrows
    Write-Logfile "OK`tUpdates for $exportmode completed, $numdeleted old records deleted."
 }
}

function Run-GroupDescriptionExport {
$dom = [ADSI]"LDAP://DC=test1,DC=test2,DC=test3"
$query = "(&(objectClass=group)(samaccountname=*))"
$table = $TableGroups
$attribs = @("samaccountname", "displayname", "description", "whencreated", "managedby", "grouptype","distinguishedname","whenchanged")
$srch = New-Object DirectoryServices.DirectorySearcher($dom, $query, $attribs)
$srch.PageSize = 1000
$srch.Sort = New-Object DirectoryServices.SortOption("sAMAccountName", [DirectoryServices.SortDirection]::Ascending)
$results = $srch.FindAll()
$count = 0
$numgroups = $results.Count
$sqlcommands = @()
$starttime = [datetime]::Now.ToUniversalTime().ToString($sqldateformat)
foreach ($res in $results) {
    $count++
    $samaccountname = $res.properties["samaccountname"][0]
    Write-Progress "Querying accounts, $count/$numgroups" $samaccountname -PercentComplete ($count * 100.0 / $numgroups)
    $displayName = ""; if ($res.properties.contains("displayname")) { $displayName = $res.properties["displayname"][0] }
    $description = ""; if ($res.properties.contains("description")) { $description = $res.properties["description"][0] }
    $managedby   = ""; if ($res.properties.contains("managedby"))   { $managedby   = $res.properties["managedby"][0] }
    $grouptype  = ""; if ($res.properties.contains("grouptype"))   { $grouptype = $res.properties["grouptype"][0] }
    $distinguishedname  = ""; if ($res.properties.contains("distinguishedname"))   { $distinguishedname   = $res.properties["distinguishedname"][0] }
    $whencreated = ""; if ($res.properties.contains("whencreated")) { $whencreated = ([datetime]$res.properties["whencreated"][0]).ToString($sqldateformat) }
    $whenchanged = ""; if ($res.properties.contains("whenchanged")) { $whenchanged = ([datetime]$res.properties["whenchanged"][0]).ToString($sqldateformat) }
    $lastupdated = [datetime]::Now.ToUniversalTime().ToString($sqldateformat)
    $sqlcommand = "DELETE FROM $table WHERE [AD_Group_Name] = '$(Esc-Quote $samaccountname)'; "
    $sqlcommand += "INSERT INTO $table ([AD_Group_Name], [AD_Group_DisplayName], [AD_Group_Description], [Last_Update], [Managed_By],[Distinguished_Name],[Group_Category],[Created_On],           AD_Last_Modified]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $displayName)', '$(Esc-Quote $description)', '$lastupdated', '$(Esc-Quote $managedby)', '$(Esc-Quote                   $distinguishedname)', '$grouptype', '$whencreated','$whenchanged')"

    $sqlcommands += $sqlcommand


    if ($sqlcommands.count -ge 100) { Run-DBCommand $sqlcommands; $sqlcommands = @() 
}
 }
Run-DBCommand $sqlcommands
if ($numgroups -eq $count) {
    Run-DBCommand @("DELETE FROM $table WHERE [Last_Update] <= '$starttime'")
}
Write-Progress " " " " -Completed
}

Open-Database
Run-GroupMemberExport "users"
Run-GroupMemberExport "computers"
Run-GroupDescriptionExport
Close-Database

1 个答案:

答案 0 :(得分:2)

这与PowerShell版本无关。您只是内存不足而已。您正在提取大量数据,因此在处理完数据后,您需要更加意识到要摆脱这些数据。

您可以执行以下几项清理内存的操作:

首先,DirectorySearcher.FindAll()的文档说:

  

由于实施限制,当进行垃圾回收时,SearchResultCollection类无法释放其所有非托管资源。为了防止内存泄漏,当不再需要SearchResultCollection对象时,必须调用Dispose方法。

因此,只要您这样做:

$results = $srch.FindAll()

请确保在完成操作后调用$results.Dispose()(在函数末尾)。

第二,当您遍历Run-GroupMemberExport函数中的结果时,您正在调用$res.GetDirectoryEntry()。通常,您只能让垃圾收集器清理DirectoryEntry对象,但是当您在这样的循环中创建大量对象时,GC没有时间运行。当我遍历数千个帐户时,这发生在我身上。

要解决此问题,您可以自己调用Dispose()对象上的DirectoryEntry。由于您已经有一个try / catch块,因此我建议添加一个finally块以确保即使发生错误也可以发生:

try {
    ...
} catch {
    Write-Logfile "Error`tCommand failed while processing $exportmode`: $($objAccount.name), $($_.exception.message)"
} finally {
    $objAccount.Dispose()
}

实际上,您可能根本根本不使用GetDirectoryEntry()。只需让DirectorySearcher返回您需要的其他属性即可。但是,如果您仍要使用它,则请确保为所需的每个属性调用RefreshCache(可以将它们全部都放在对RefreshCache的调用中)。如果访问Properties集合并要求它在缓存中尚不存在的值,则它将向AD询问每个具有值的属性-这是很多不必要的数据。