Get-ADUserフィルターを最適化する

Nov 29 2020

ADで、同じEmployeeID値が2つ以上のレコードに入力されているユーザーアカウントを特定しようとしています。以下は私のコードです(クレジット:ここでShow-Progress定義された関数を使用しています)。Get-ADUserコマンドだけですべてのレコードをフェッチするのに2時間以上かかりました。他のステップ(2から5)はかなり迅速でした。作業を完了している間、PowerShellを使用してこれをより効率的に実行できたかどうかを確認しようとしています。

Get-ADUser -LDAPFilter "(&(ObjectCategory=Person)(objectclass=user)(employeeid=*))" -Properties $properties -Server $server_AD_GC -ResultPageSize 1000 | 
    # *ISSUE HERE*
    #    The Get-ADUser extract process seems to work very slow.
    #    However, it is important to note that the above command will be retrieving more than 200K records
    # NOTE: I've inferred that employeeid is an indexed attribute and is replicated to GlobalCatalogs and hence have used it in the filter
    Show-Progress -Activity "(1/5) Getting AD Users ..." |
select $selectPropsList -OutVariable results_UsersBaseSet | Group-Object EmployeeID | Show-Progress -Activity "(2/5) Grouping on EmployeeID ..." | ? { $_.Count -gt 1 } | 
    Show-Progress -Activity "(3/5) Filtering only dup EmpID records ..." | 
select -Exp Group | 
    Show-Progress -Activity "(4/5) UnGrouping ..." | 
Export-Csv "C:\Users\me\op_GetADUser_w_EmpID_Dupes_EntireForest - $([datetime]::Now.ToString("MM-dd-yyyy_hhmmss")).csv" -NoTypeInformation |
    Show-Progress -Activity "(5/5) Exporting ..." | 
Out-Null

PS:最初にすべてのユーザーアカウントをcsvファイルにエクスポートしてから、Excelで後処理しようとしましたが、データセットのサイズが原因で眉をひそめなければならず、時間とメモリの両方が不足していました。

どんな提案でも大歓迎です。

回答

2 Theo Nov 29 2020 at 16:20

我々はしているかわからないので$propertiesまたは$selectPropsList、あなたの質問は、同じ社員が、右発行されたユーザーにアウト見つけることについては本当にありますか?
デフォルトでは、Get-ADUserはすでに次のプロパティを返します。

DistinguishedNameEnabledGivenNameNameObjectClassObjectGUIDSamAccountNameSIDSurnameUserPrincipalName

したがって、追加で必要なのは、私が推測するEmployeeIDだけです。たくさんのプロパティを収集しようとすると速度が低下するため、これを最小限に抑えることで処理速度を上げることができます。

次に、Show-Progressリンクしたスクリプトを使用すると、スクリプトの実行が大幅に遅くなります。あなたは本当にプログレスバーを持っている必要がありますか?アクティビティステップを含む行をコンソールに直接書き込んでみませんか?

また、すべてを一緒に配管することは、スピード部門でも役に立ちません。

$server_AD_GC = 'YourServer' $selectPropsList = 'EmployeeID', 'Name', 'SamAccountName', 'Enabled'
$outFile = "C:\Users\me\op_GetADUser_w_EmpID_Dupes_EntireForest - $([datetime]::Now.ToString("MM-dd-yyyy_hhmmss")).csv"

Write-Host "Step (1/4) Getting AD Users ..." 
$users = Get-ADUser -Filter "EmployeeID -like '*'" -Properties EmployeeID -Server $server_AD_GC -ResultPageSize 1000

Write-Host "Step (2/4) Grouping on EmployeeID ..."
$dupes = $users | Group-Object -Property EmployeeID | Where-Object { $_.Count -gt 1 } Write-Host "Step (3/4) Collecting duplicates ..." $result = foreach ($group in $dupes) {
    $group.Group | Select-Object $selectPropsList
}

Write-Host "Step (4/4) Exporting ..."
$result | Export-Csv -Path $outFile -NoTypeInformation

Write-Host  "All done" -ForegroundColor Green

PSはGet-ADUserすでにユーザーオブジェクトのみを返すため、LDAPフィルターは必要ありません(ObjectCategory=Person)(objectclass=user)。使用-Filter "EmployeeID -like '*'"はおそらくより速いです

1 mklement0 Nov 29 2020 at 22:27

この答えは、補完テオの役に立つ答えをし、に焦点を当てて進捗を示す動作中

  • この記事の執筆時点で最新のリンクされたShow-Progress関数:

    • パイプライン入力を通過させないという点で、完全なバグがあります(関連する行が誤ってコメントアウトされています)

    • processブロックを使用しないという概念上の欠陥があります。つまり、すべてのパイプライン入力は、処理される前に最初収集されます。これは、プログレスバーの概念を無効にします。

  • したがって、Show-Progressパイプライン内の前のコマンドがすべての出力を出力するまで、呼び出しは進行状況を表示しません。簡単な代替方法は、パイプラインを個別のコマンドに分割し、各コマンドの前に1つの進行状況メッセージを出力して、Theoの回答に示されているように、処理の次の段階(オブジェクトごとの進行状況ではなく)を通知することです。

  • 一般に、コマンド内部処理の進行状況を表示する方法はなく、コマンドの(マルチオブジェクト)出力の進行状況のみを表示します

    • をForEach-Object呼び出す呼び出しを介してこれを行う最も簡単な方法
      Write-Progressですが、2つの課題があります。

      • 完了率の進行状況バーを表示するには、合計いくつのオブジェクトがあるかを知る必要があります。パイプラインは受け取るオブジェクトの数を知ることができないため、事前に決定する必要があります。唯一のオプションは、最初にすべての出力収集し(またはそれをカウントする他の方法を見つけて)、収集した出力をパイプライン入力として使用し、オブジェクトのカウントをに渡す値を計算するための基礎として使用することです。Write-Progress -PerCentComplete

      • 受信Write-Progressしたオブジェクトを呼び出すと、処理全体が大幅に遅くなります。妥協案は、この回答に示されているように、N個のオブジェクトごとにのみ呼び出すことです。そこでのアプローチShow-Progressは、引数として合計オブジェクト数を渡す必要があり、適切なストリーミング入力オブジェクト処理を(processブロックを介して)実行する、適切に実装された関数alaにラップすることができます。とはいえ、入力オブジェクトを渡すためにPowerShellコードを使用するという単なる行為にはコストがかかります。


結論:

完了率の表示には、2つの固有の問題があります

  • 事前に処理するオブジェクトの総数を知っておく必要があります(パイプラインには、通過するオブジェクトの数を知る方法がありません)。

    • いずれか:可能であれば、事前にメモリ内で処理するすべてのオブジェクトを収集します。コレクション内の要素の数は、完了率の計算の基礎として機能します。これは、入力セットが非常に大きいオプションではない場合があります。

    • または:実際にオブジェクトを取得せずにすべてのオブジェクトをカウントするだけの追加の処理ステップを事前に実行します。これは、追加の処理時間が追加されるという点で実用的でない場合があります。

  • PowerShellコードでのオブジェクトごとの処理(経由ForEach-Objectまたは高度なスクリプト/関数)は、本質的に低速です。

    • この回答にWrite-Progress示されているように、N個のオブジェクトごとに呼び出しを制限することで、これをいくらか軽減できます。

全体として、これは処理速度とエンドユーザーに完了率を表示する機能との間トレードオフです。