【问题标题】:Trying to return AD Group members with extended info, but output CSV is showing unexpected, repeated data尝试使用扩展信息返回 AD 组成员,但输出 CSV 显示意外的重复数据
【发布时间】:2022-01-27 10:32:05
【问题描述】:

我的目标是转储我们的 AD 组、他们的成员以及这些成员对象是否已启用的 CSV,但我遇到了一个奇怪的(可能是自己造成的)问题,其中 Foreach-Object 循环的行为没想到。

输出几乎有效。它转储一个 CSV 文件。该文件包含每个组的行,填充了正确的组相关数据,以及正确的行数,跟在组成员的数量之后。但是,这些行上的组成员属性会重复,为每个组成员结果反复显示相同的用户数据,显然遵循 Get-ADGroupMember 中最后返回的对象的属性。

为了尝试诊断问题,我添加了Write-Host $GroupMember.Name -ForegroundColor Gray 这一行。这就是我知道 CSV 中的条目是每个组的最后返回结果的方式。令人困惑的是,控制台正确地回显了每个组成员的显示名称。

我假设这里存在某种逻辑错误,但我没有找到它。任何帮助将不胜感激!

clear
Import-Module ActiveDirectory

# CONFIG ========================================
# Plant Number OU to scan. Used in $CSV and in Get-ADComputer's search base.
$PlantNumber = "1234"
# FQDN of DC you want to query against. Used by the Get-AD* commands.
$ServerName = "server.com"
# Output directory for the CSV. Default is [Environment]::GetFolderPath("Desktop"). Used in $CSV. NOTE: If setting up as an automated task, change this to a more sensible place!
$OutputDir = [Environment]::GetFolderPath("Desktop")
# CSV Output string. Default is "$OutputDir\$PlantNumber"+"-ComputersByOS_"+"$(get-date -f yyyy-MM-dd).csv" (+'s used due to underscores in name)
$CSV = "$OutputDir\$PlantNumber"+"GroupMembers_"+"$(get-date -f yyyy-MM-dd).csv"

# Create empty array for storing collated results
$collectionTable = @()

# Get AD groups, return limited properties
Get-AdGroup -filter * -Property Name, SamAccountName, Description, GroupScope -SearchBase "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM" -server $ServerName | Select SamAccountName, Description, GroupScope | Foreach-Object {
    Write-Host "Querying" $_.SamAccountName "..."
    #Initialize $collectionRow, providing the columns we want to collate
    $collectionRow = "" | Select GroupName, GroupScope, GroupDesc, MemberObjectClass, MemberName, MemberDisplayName, Enabled

    # Populate Group-level collectionRow properties
    $collectionRow.GroupName = $_.SamAccountName
    $collectionRow.GroupDesc = $_.Description
    $collectionRow.GroupScope = $_.GroupScope

    # Process group members
    Get-ADGroupMember -Identity ($collectionRow.GroupName) -Server $ServerName -Recursive | ForEach-Object {
        $GroupMember = $_
        # Echo member name to console
        Write-Host $GroupMember.Name -ForegroundColor Gray

        $collectionRow.MemberName = $GroupMember.SamAccountName
        $collectionRow.MemberDisplayName = $GroupMember.name
        $collectionRow.MemberObjectClass = $GroupMember.ObjectClass

        # If the member object is a user, collect some additional data
        If ($collectionRow.MemberObjectClass -eq "user") {
            Try {
                $collectionRow.Enabled = (Get-ADUser $GroupMember.SamAccountName -Property Enabled -ErrorAction Stop).Enabled
                If ($collectionRow.Enabled -eq "TRUE") {$collectionTable += $collectionRow}
            }
            Catch {
                $collectionRow.Enabled = "ERROR"
                $collectionTable += $collectionRow
            }
            
        }
        
        
    }
}

Write-Host "`n"

# Attempt to save results to CSV. If an error occurs, alert the user and try again.
$ExportSuccess = 'false'
while ($ExportSuccess -eq 'false') {
    Try 
    {
        # Export results to $CSV
        $collectionTable| Export-csv $CSV -NoTypeInformation -ErrorAction Stop
        # If the above command is successful, the rest of the Try section will execute. If not, Catch is triggered instead.
        $ExportSuccess = 'true'
        Write-Host "`nProcessing complete. Results output to"$CSV
    }
    Catch
    {
        Write-Host "Error writing to"$CSV"!" -ForegroundColor Yellow
        Read-Host -Prompt "Ensure the file is not open, then press any key to try again"
    }

}

【问题讨论】:

  • ....在我看来,这个问题更多地属于code review而不是SO。
  • @Olaf,我认为它不符合他们的发帖规则,所以它可能不适合那里。
  • hmmm ...好的,对我来说,您的代码看起来很混乱。但也许一些一般提示可以帮助你。根据您的 AD 结构,您最终可能会多次查询相同的用户。相反,您可以一次将所有用户收集到一个临时变量中并使用它。对于您的输出,我建议使用PSCustomObject

标签: powershell csv foreach


【解决方案1】:

您的代码中有很多地方需要修复,我只指出最重要的:

  • 不要使用@() and +=
  • 您继续使用'True''False',它们是字符串,PowerShell booleans$true$false

还有太多的冗余代码。 ForEach-Object 也很慢,如果您的群组有很多成员,并且由于您使用的是 -Recursive,最好改用 fast loop

$PlantNumber = "1234"
$ServerName = "server.com"
$OutputDir = [Environment]::GetFolderPath("Desktop")
$fileName = "${PlantNumber}GroupMembers_$(Get-Date -f yyyy-MM-dd).csv"
$CSV = Join-Path $OutputDir -ChildPath $fileName

# $collectionTable = @() => Don't do this to collect results, ever

$adGroupParams = @{
    # Name and SAM are default, no need to add them
    Properties = 'Description', 'GroupScope'
    SearchBase = "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM"
    Server     = $ServerName
    Filter     = '*'
}

# Get AD groups, return limited properties
$collectionTable = foreach($group in Get-AdGroup @adGroupParams)
{
    Write-Host "Querying $($group.samAccountName)..."
    foreach($member in Get-ADGroupMember $group -Server $ServerName -Recursive)
    {
        # if this member is 'user' the Enabled property
        # will be a bool ($true / $false) else it will be $null
        $enabled = if($member.ObjectClass -eq 'User')
        {
            (Get-ADUser $member).Enabled
        }
    
        [pscustomobject]@{
            GroupName         = $group.SamAccountName
            GroupDesc         = $group.Description
            GroupScope        = $group.GroupScope
            MemberName        = $member.SamAccountName
            MemberDisplayName = $member.Name
            MemberObjectClass = $member.ObjectClass
            Enabled           = $enabled
        }
    }
}

【讨论】:

    【解决方案2】:

    据我了解,您需要将包含成员的群组列表导出到 csv 文件,并知道是否启用了成员帐户,如果这是您想要的,您可以查看以下代码

    $output = @()
    Import-Module ActiveDirectory
    $ServerName = "server.com"
    $PlantNumber = "1234"
    $OutputDir = [Environment]::GetFolderPath("Desktop")
    $CSV = "$OutputDir\$PlantNumber"+"GroupMembers_"+"$(get-date -f yyyy-MM-dd).csv"
    
    
    $groups = Get-AdGroup -filter * -Property Description -SearchBase "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM" -server $ServerName
    
    foreach ($group in $groups){
    $members = Get-ADGroupMember -Identity $group.SamAccountName -Recursive
        foreach ($member in $members){
            $output += [pscustomobject]@{
            GroupName = $group.SamAccountName
            GroupDesc = $group.Description
            GroupScope = $group.GroupScope
            MemberName = $member.samaccountname
            MemberDisplayName = $member.Name
            MemberObjectClass = $member.ObjectClass
            Enabled = $(Get-ADUser -Identity $member.samaccountname).enabled
    
            }
        }
    }
    $output | Export-Csv $CSV -NoTypeInformation 
    

    【讨论】:

      【解决方案3】:

      我明确没有引用您的代码。我只想展示我将如何处理这项任务。无论如何,我希望它会对你有所帮助。

      $Server = 'Server01.contoso.com'
      $SearchBase = 'OU=BaseOU,DC=contoso,DC=com'
      $CSVOutputPath = '... CSV path '
      
      $ADGroupList = Get-ADGroup -Filter * -Properties Description -SearchBase $SearchBase -Server $Server
      $ADUserList  = Get-ADUser  -Filter * -Properties Description -SearchBase $SearchBase -Server $Server
      $Result = 
      foreach ($ADGroup in $ADGroupList) {
          $ADGroupMemberList = Get-ADGroupMember -Identity $ADGroup.sAMAccountName -Recursive
          foreach ($ADGroupmember in $ADGroupMemberList) {
              $ADUser = $ADUserList | Where-Object -Property sAMAccountName -EQ -Value $ADGroupmember.sAMAccountName
              [PSCustomObject]@{
                  ADGroupName                 = $ADGroup.Name
                  ADGroupDescription          = $ADGroup.Description
                  ADGroupMemberName           = $ADUser.Name
                  ADGroupMemberSamAccountName = $ADUser.sAMAccountName
                  ADGroupMemberDescription    = $ADUser.Description
                  ADGroupMemberStatus         = if ($ADUser.Enabled) { 'enabled' }else { 'diabled' }
              }
          }
      }
      $Result |
          Export-Csv -Path $CSVOutputPath -NoTypeInformation -Delimiter ',' -Encoding utf8
      

      它只会输出几个属性,但我希望你明白。

      顺便说一句:属性 DistinguishedName、Enabled、GivenName、Name、ObjectClass、ObjectGUID、SamAccountName、SID、Surname、UserPrincipalName 包含在 Get-ADUser 的默认返回集中以及属性 DistinguishedName、GroupCategory、GroupScope、Name、ObjectClass、 ObjectGUID、SamAccountName、SID 包含在Get-ADGroup 的默认返回集中。您无需使用参数-Properties 显式查询它们。

      【讨论】:

      • 感谢大家的回复!最后,按照您的所有示例,我按照 Olaf 的建议使用了 PSCustomObject。它现在工作正常!但是,我仍然对为什么某些值会返回它们在 Powershell 中所做的事情感到困惑,并将提交一个单独的问题,并提供一个不那么复杂的示例。再次感谢!
      猜你喜欢
      • 2013-08-27
      • 1970-01-01
      • 2017-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-13
      • 1970-01-01
      • 2021-12-02
      相关资源
      最近更新 更多