Сбой сценария импорта PowerShell Active Directory с PS 3.0 или выше - PullRequest
0 голосов
/ 26 ноября 2018

Я мало что знаю о PowerShell, но унаследовал скрипт от кого-то, кто больше не доступен для помощи.Этот скрипт импортирует информацию о группе AD и членство, связанное с пользователями и компьютерамиОн работает нормально при запуске на машине с PS 2.0, но вылетает, если выполняется на PS 3.0 или новее.

Я не смог выяснить, что нужно изменить, но, похоже, ошибки начинают появляться вШаг импорта членства «Компьютер», и есть сотни ошибок, которые все говорят:

Команда не выполнена при обработке компьютеров:, Исключение типа 'System.OutOfMemoryException' было выдано

Затем, в какой-то момент, похоже, что скрипт просто останавливается и даже не достигает 3-го шага / функции.

Любой совет?

[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 голосов
/ 26 ноября 2018

Это никак не связано с версией PowerShell.Тебе просто не хватает памяти.Вы извлекаете много данных, поэтому вам нужно больше осознавать, как избавиться от этих данных, когда закончите с ними.

Есть несколько вещей, которые вы можете сделать, чтобы очистить память:

Во-первых, документация для DirectorySearcher.FindAll() гласит:

Из-за ограничений реализации класс SearchResultCollection не может освободить все свои неуправляемые ресурсы, когда он является мусоромсобраны.Чтобы предотвратить утечку памяти, вы должны вызывать метод Dispose, когда объект SearchResultCollection больше не нужен.

Поэтому всякий раз, когда вы делаете:

$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 каждый атрибут со значением - это много ненужных данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...