【问题标题】:insert large amount of AD data into SQL server using PowerShell使用 PowerShell 将大量 AD 数据插入 SQL Server
【发布时间】:2017-05-12 22:21:19
【问题描述】:

我有一个 PowerShell 脚本,它提取 1.4+ 百万行数据并将其保存到一个巨大的 CSV 文件中,然后将其导入 SQL 服务器。我认为可能有一种方法可以让 PowerShell 直接将数据插入 SQL 服务器,但我不确定如何。

我担心的一个问题是我不想将 AD 结果缓冲到内存中然后再写入它们。我宁愿分批写 1000 个或其他东西,这样内存消耗就会下降。获取 1000 条记录,保存到 SQL Server,然后重复...

我看到有关如何让 PowerShell 写入 SQL 服务器的文章,但它们似乎要么一次处理所有数据,要么一次处理一条记录——这两种方法对我来说似乎都效率低下。

这是我必须查询 AD 的 PowerShell 脚本。

# the attributes we want to load
$ATTRIBUTES_TO_GET = "name,distinguishedName"

# split into an array
$attributes = $ATTRIBUTES_TO_GET.split(",")

# create a select string to be used when we want to dump the information
$selectAttributes = $attributes | ForEach-Object {@{n="AD $_";e=$ExecutionContext.InvokeCommand.NewScriptBlock("`$_.$($_.toLower())")}}

# get a directory searcher to search the GC
[System.DirectoryServices.DirectoryEntry] $objRoot = New-Object System.DirectoryServices.DirectoryEntry("GC://dc=company,dc=com")
[System.DirectoryServices.DirectorySearcher] $objSearcher = New-Object System.DirectoryServices.DirectorySearcher($objRoot)

# set properties
$objSearcher.SearchScope = "Subtree"
$objSearcher.ReferralChasing = "All"

# need to set page size otherwise AD won't return everything
$objSearcher.PageSize = 1000

# load the data we want
$objSearcher.PropertiesToLoad.AddRange($attributes)

# set the filter
$objSearcher.Filter = "(&(objectClass=group)(|(name=a*)(name=b*)))"

# get the data and export to csv
$objSearcher.FindAll() | select -expandproperty properties | select $selectAttributes | export-csv -notypeinformation -force "out.csv"

【问题讨论】:

  • 考虑批量使用SqlBulkCopy。 stackoverflow.com/questions/43679921/…
  • 我认为bulk insert 的效率差不多。你为什么不相信?
  • @DanGuzman 但是我将如何使用System.DirectoryServices.SearchResultCollection 对象批量执行此操作?我能想到的唯一方法是遍历 SearchResultCollection 并在 X # 条记录之后进行批量 SQL 插入?
  • @AnsgarWiechers 我不反对。我只是不知道如何使用 SearchResultCollection 对象进行批量插入,而无需将整个集合加载到内存中。

标签: sql-server powershell active-directory


【解决方案1】:

我使用Out-DataTable 将我的对象数组转换为DataTable 对象类型,然后使用Write-DataTable 将其批量插入到数据库中(Write-DataTable 使用SqlBulkCopy 来执行此操作)。

注意事项/注意事项(SqlBulkCopy 可能会令人讨厌地进行故障排除):

  • 确保您的属性是正确的类型(字符串表示 varchar/nvarchar,int 表示任何整数值,dateTime 可以是字符串,只要格式正确并且 SQL 可以解析它)
  • 确保您的属性井井有条,并与您要插入的表格对齐,包括自动填充的所有字段(递增 ID 键、RunDt 等)。

输出数据表:https://gallery.technet.microsoft.com/scriptcenter/4208a159-a52e-4b99-83d4-8048468d29dd

写入数据表:https://gallery.technet.microsoft.com/scriptcenter/2fdeaf8d-b164-411c-9483-99413d6053ae

用法

如果我继续您的示例并跳过 CSV,我会这样做...用下面的代码替换最后两行(假设您的对象属性与表格完美对齐,您的 SQL服务器名称为sql-server-1,数据库名称为org,表名称为employees):

try {
    Write-DataTable -ServerInstance sql-server-1 -Database org -TableName employees -Data $($objSearcher.FindAll() | Select-Object -expandproperty properties | Select-Object $selectAttributes | Out-DataTable -ErrorAction Stop) -ErrorAction Stop
}
catch {
    $_
}

【讨论】:

  • 但是第一行,$set = ... 不会首先将所有内容加载到内存中吗?有没有办法只将 X # 行加载到内存中,写入 SQL,然后再加载下 X # 行?
  • 如果您对 AD 的查询正在提取所有内容,那么它已经加载到内存中。你需要收紧你的查询,否则这是一个无关紧要的问题。我已将我的更新为包含在 try catch 语句中的单行,因此它会立即加载到数组中,转换为数据表,然后批量插入到数据库中。考虑到您的 $objSearcher.PageSize = 1000 位已经受到限制,也许可以转换它以便在循环中一次批量插入 1000 条记录?
  • 我会说我使用相同的方法一次将 75,000 多行加载到 SQL 中,其中很多是 nvarchar(max) 字段。它可以处理很大的负载,我认为您可以节省大量时间一次处理更大的块
【解决方案2】:

查看您的代码,您似乎来自 .NET 或某种基于 .NET 的语言。您是否听说过 cmdlet Get-ADUser / Get-ADGroup?这将为您大大简化事情。

就 SQL 连接而言,PowerShell 没有任何本机支持。微软已经为它制作了cmdlets!您只需要安装 SQL Server 才能获得它们……这有点令人遗憾,因为 SQL 如此繁重,并不是每个人都想安装它。使用 .NET 仍然是可能的,只是不是很快或不是很漂亮。我不会在这里给出关于 cmdlet 的建议,你可以谷歌一下。至于 .NET,我将首先阅读一些关于 System.Data.SqlClient 命名空间的文档以及一些关于该主题的 historical questions

最后,正如您所说,尝试避免 RAM 过载是个好主意。这里最重要的是尝试将整个脚本缩减为一个 AD 查询。这样,您就可以避免在一个查询和下一个查询之间发生数据更改的麻烦情况。我认为最好的方法是将结果直接保存到文件中。一旦你有了它,你可以使用SqlBulkCopy 直接从你的文件中插入表格。这样做的缺点是它不允许多个 AD 属性。至少我不认为 SqlBulkCopy 会允许这样做?

Get-ADUser "SomeParamsHere" | Out-File ADOutput.txt

如果您必须拥有多个 AD 属性并且仍然希望将 RAM 使用量保持在最低限度...好吧,我玩弄了一个可以工作的脚本,但会进行一些从整个文件中读取的调用,这会失败整个目的。您最好的选择可能是将每个属性保存到一个单独的文件中,然后执行整个写入数据库操作。示例:

New-Item Name.txt
New-Item DistinguishedName.txt

Get-ADUser "SomeParamsHere" -Properties "Name,DistinguishedName" | Foreach {
    Add-Content -Path "Name.txt" -Value "$_.Name"
    Add-Content -PassThru "DistinguishedName.txt" -Value "$_.DistinguishedName"
}

【讨论】:

  • 我听说过Get-ADUser,但认为.NET 更快、更高效。不是这样吗?如果我先保存到文件,那么它是一个多步骤过程:获取数据、写入文件、从文件读取并写入 SQL。必须有一种方法可以批量获取数据并直接写入 SQL 吗?就像读取 1000 行 AD 数据,写入 SQL,然后读取接下来的 1000 行......
  • 我不确定哪个更有效。您可以使用 Measure-Command cmdlet 进行测试以找出答案。我很想知道。但是你在你的问题中没有提到 CPU 效率,所以我根本没有关注这一点。尽管您不能拥有 RAM 和 CPU 效率,但要现实一点。他们互相矛盾。你必须选择其中之一。我的回答优先考虑降低 RAM 使用率。
【解决方案3】:

将结果存储在变量的最后一行代码中,而不是将其导出到 csv。
然后创建你想要的大小的组。
使用 Out-DataTable 和 Write-DataTable 写入 SQL - nferrell 答案中的链接。

$res = $objSearcher.FindAll() | select -expandproperty properties | select 
$selectAttributes 
$counter = [pscustomobject] @{ Value = 0 }
#create groups with 1000 entries each 
$groups = $res | Group-Object -Property { [math]::Floor($counter.Value++ / 1000) }
foreach ($group in $groups){
    #convert to data table
    $dt = $group.group | Out-DataTable
    $dt | Write-DataTable -Database DB -ServerInstance SERVER -TableName TABLE 
}

`

【讨论】:

    【解决方案4】:

    你让这变得不必要地复杂了。 如果我正确阅读了您的代码,您希望所有组都以“a”或“b”开头。

    # the attributes we want to export
    $attributes = 'name', 'distinguishedName'
    
    Import-Module ActiveDirectory
    
    Get-ADGroup -Filter {(name -like "a*") -or (name -like "b*")} -SearchBase 'dc=company,dc=com' |
        select $attributes | Export-Csv -NoTypeInformation -Force "out.csv"
    

    不要在最后使用Export-Csv,只需将输出通过管道传送到创建 SQL 行的命令。通过管道对象(而不是将它们分配给变量),您可以让 PowerShell 有效地处理它们(它会在对象进入时开始处理它们,而不是缓冲所有内容)。

    很遗憾,我无法在 SQL 部分为您提供帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-10
      • 2015-02-06
      • 1970-01-01
      • 1970-01-01
      • 2011-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多