Powershell 递归函数的行为不正确

Powershell 递归函数的行为不正确,powershell,recursion,Powershell,Recursion,我们在active directory中具有以下组成员身份结构: BEL Test Top level - BEL Test Sub level 1 - Bob - BEL Test Sub level 1.1 - Jake - Mike - BEL Test Sub level 2 - BEL Test Sub level 2.1 所需输出: GroupName : BEL

我们在active directory中具有以下组成员身份结构:

BEL Test Top level
    - BEL Test Sub  level 1
        - Bob
        - BEL Test Sub  level 1.1
            - Jake
            - Mike
    - BEL Test Sub  level 2
        - BEL Test Sub  level 2.1
所需输出:

GroupName : BEL Test Top level
Member1   : BEL Test Sub  level 1
Member2   : BEL Test Sub  level 1.1
Member3   : Jake

GroupName : BEL Test Top level
Member1   : BEL Test Sub  level 1
Member2   : BEL Test Sub  level 1.1
Member3   : Mike

GroupName : BEL Test Top level
Member1   : BEL Test Sub  level 1
Member2   : Bob

GroupName : BEL Test Top level
Member1   : BEL Test Sub  level 2
Member2   : BEL Test Sub  level 2.1
因此,对于每个最深的对象,都需要一个
[PSCustomObject]
作为输出。我不是递归函数方面的专家,但我编写的以下代码非常接近:

$Name = 'BEL Test Top level'

$hash = $null

Function Add-MemberGroupHC {
    Param (
        [Parameter(Mandatory, ValueFromPipeline)]
        $Identity,
        $Past,
        [Int]$Level
    )

    Begin {
        if (-not $Level) {
            $Level = 0
        }

        if (-not $Past) {
            $Past = [Ordered]@{
                GroupName = $Name
            }
        }

        if ($Identity.GetType().Name -ne 'ADPrincipal') {
            $Identity = Get-ADGroup -Identity $Identity
        }
    }

    Process {
        $Level++

        Write-Verbose "Check members '$($Identity.Name)'"

        $Members = Get-ADGroupMember $Identity 

        $Members | ForEach-Object {
            Write-Verbose "Add property '$('Member' + $Level)' value '$($_.Name)'"
            $Past.('Member' + $Level) = $_.Name

            if (($_.ObjectClass -eq 'User') -or (-not (Get-ADGroupMember $_))) {
                  [PSCustomObject]$Past
            }
                $Past.('Member' + $Level) = $_.Name
                [PSCustomObject]$Past
            }

            if ($_.ObjectClass -eq 'Group') {
                Add-MemberGroupHC -Identity $_ -Past $Past -Level $Level
            }
        }

    }
}

$Result = Add-MemberGroupHC $Name
$Result | fl *

当没有孩子的时候,怎么可能让它只输出最深层次的信息呢?

我想用我自己的方法从一开始就了解它是如何工作的。我不能说这比你预期的方法好还是坏。希望你能用这个来看看哪里出了问题

Function Get-HighestMemberKey{
    param([hashtable]$HashTable)

    # Collect all of the member# names. Find the highest one. 
    # If one does not exist null gets cast to 0 with [int]
    return [int](($HashTable.GetEnumerator()) | 
        Select -ExpandProperty Name | 
        Where-Object{$_ -match "(\d+)$"} |
        ForEach-Object{$Matches[0]} |
        Measure-Object -Maximum | 
        Select -ExpandProperty Maximum)
}

function Get-ADMembersGroupChain{
    param(
        $GroupName,
        $CurrentChain
    )

    $CurrentMembers = @(Get-ADGroupMember $GroupName)

    # Check if this group has any members.
    if($CurrentMembers.Count -gt 0){
        # If there are any groups process them individually
        $CurrentMembers | ForEach-Object{

            if(!$CurrentChain){
                # This is a root group. Start a new chain.
                $CurrentChain = @{GroupName=$GroupName}
            }

            # Add this member to the chain. 
            # Create a new chain for this pass. Use clone to ensure we are working with a new chain. 
            $nextMemberIndex = (Get-HighestMemberKey $CurrentChain) + 1
            $newChain = $CurrentChain.Clone()
            $newChain."Member$nextMemberIndex" = $_.Name

            # If this is a group continue the chain. 
            if($_.ObjectClass -eq "group"){
                Get-ADMembersGroupChain -GroupName $_.SamAccountName -CurrentChain $newChain
            } else {
                # This is a user. Output the chain
                [pscustomobject]$newChain
            }
        }
    } else {
        # The group is already part of the chain. Ouput as is. 
        [pscustomobject]$CurrentChain
    }
}

$chains = Get-ADMembersGroupChain "BEL Test Top level" 
$chains | ForEach-Object{$_| fl}
我们在这里要做的是构建递归传递给函数的哈希表。遇到组时,将再次调用该函数。如果有一个组有0个成员,或者如果找到一个用户,那么到目前为止,该链将转换为一个psobject并发送到管道中

这有一个小的副作用,因为您无法保证成员的显示顺序。如果这是一个问题,您将看到构建自己的select语句


样本输出 如果不使用
格式列表
,则输出可能不正确,因为PowerShell将基于管道中的第一个对象显示,但所有属性都将显示在那里。如果这是一个问题,那么您需要创建一个小函数来保证属性输出的顺序。一个基本的例子是:

function Order-Chain{
    param(
        $chain
    )

    # Take the group and members and ensure the are output in numerical order. 
    # Assume there is at least a property called GroupName
    $properties = @("GroupName")
    # Get all the remaining property names minus the first one.
    $properties += $chain.psobject.properties.name | Where-Object{$_ -notin $properties} | 
        # Sort the property list on the number at the end of the property name
        Sort-Object -Property {[void]($_ -match "\d+$");$matches[0]}

    # Order the chain and send down the pipe
    $chain | Select-Object $properties
} 
这将创建一个已排序的属性列表,该列表将被馈送到
选择对象
。在创建哈希表时,使用
[ordered]
似乎更智能/更容易,但您无法克隆有序哈希,因此这就是我的解决方法


这里的所有函数都可以变得更加健壮,即像您一样使用
begin
块,并转换为高级函数,但现在功能正常注意循环组,因为没有逻辑来检测这些循环组

我只是想发布我是如何最终修复错误代码的。感谢@Matt的大力帮助。下面是检查循环组成员资格的完整代码

希望这能帮助任何遇到与我相同或类似问题的人

Function Get-ADGroupMemberFlatHC {
<# 
    .SYNOPSIS   
        Get AD group membership and create an object for eash deepest AD Object.

    .DESCRIPTION
        Retrieve all the members of an active directory group. When a group doesn't contain any members we output this group. If it does contain other members we output only the users and do the same for group members. In the end, the output will contain one object per deepeest node, which can be an AD group or AD user.

    .PARAMETER Identity 
        The group name in Strng format or ADPrincipal format #>

    Param (
        [Parameter(Mandatory, ValueFromPipeline)]
        $Identity
    )

    Begin {
        Function Add-MemberGroupHC {
            Param (
                [Parameter(Mandatory)]
                $Identity,
                [Parameter(Mandatory)]
                $Past,
                [Parameter(Mandatory)]
                [Int]$Level
            )

            Process {
                Write-Verbose "Check members '$($Identity.Name)'"
                $Past = Remove-ExcessHashItemsHC $Past ('Member' + ($Level+1))

                Get-ADGroupMember $Identity | ForEach-Object {
                    $Past.('Member' + ($Level+1)) = $_.Name

                    if ($CircularGroup = Test-CircularGroupMembershipHC $Past) {
                        $CircularGroup
                    }
                    else {
                        if (($_.ObjectClass -eq 'User') -or (-not (Get-ADGroupMember $_))) {
                            Write-Verbose "End node '$($_.Name)'"
                            [PSCustomObject]$Past
                        }
                        elseif ($_.ObjectClass -eq 'Group') {
                            Write-Verbose "Member group '$($_.Name)'"
                            Add-MemberGroupHC -Identity $_ -Past $Past -Level ($Level+1)
                        }
                    }
                }
            }
        }

        Function Remove-ExcessHashItemsHC {
            Param (
                [Parameter(Mandatory)]
                [hashtable]$HashTable,
                [Parameter(Mandatory)]
                [String]$Key
            )

            $OrderedHashTable = [Ordered]@{}

            $HashTable.GetEnumerator() | Sort-Object Name | where Name -LT $Key | ForEach-Object {
                $OrderedHashTable.($_.Name) = $_.Value
            }

            $OrderedHashTable
        }

        Function Test-CircularGroupMembershipHC {
            Param (
                [Parameter(Mandatory)]
                [HashTable]$HastTable
            )

            $HastTable.Values | Group-Object | where Count -GE 2 | ForEach-Object {
                Write-Warning "Circular group memberships found for group '$($_.Name)'"
                $Past[$Past.Count -1] = ($Past[-1] + ' *')
                [PSCustomOBject]$Past
            }
        }
    }

    Process {
        Try {
            $Past = [Ordered]@{}

            if ($Identity.GetType().Name -ne 'ADPrincipal') {
                $Identity = Get-ADGroup -Identity $Identity
            }

            Write-Verbose "Add property 'GroupName' value '$($Identity.Name)'"
            $Past.GroupName = $Identity.Name

            Add-MemberGroupHC -Identity $Identity -Past $Past -Level 0
        }
        Catch {
            throw "Failed retrieving members for group '$Identity': $_"
        }
    }
}
函数获取ADGroupMemberFlatHC{

@DarkLite1我添加了一些东西来弥补PowerShell在这件事上的行为。我现在会看看你的函数,看看是否能找出发生了什么事。Thx Matt!我总是想知道为什么你的代码看起来比我的代码专业得多…我得赶一段时间。现在学习Java课程来填补逻辑思维上的一些空白。Thx man,非常感谢:)感谢关于循环组的提示。我将在代码中添加一些检查。您当前的输出是什么?我还认为您缺少一个
else
,或者在最后一个if块中有一个额外的括号。可能是复制粘贴错误?
Function Get-ADGroupMemberFlatHC {
<# 
    .SYNOPSIS   
        Get AD group membership and create an object for eash deepest AD Object.

    .DESCRIPTION
        Retrieve all the members of an active directory group. When a group doesn't contain any members we output this group. If it does contain other members we output only the users and do the same for group members. In the end, the output will contain one object per deepeest node, which can be an AD group or AD user.

    .PARAMETER Identity 
        The group name in Strng format or ADPrincipal format #>

    Param (
        [Parameter(Mandatory, ValueFromPipeline)]
        $Identity
    )

    Begin {
        Function Add-MemberGroupHC {
            Param (
                [Parameter(Mandatory)]
                $Identity,
                [Parameter(Mandatory)]
                $Past,
                [Parameter(Mandatory)]
                [Int]$Level
            )

            Process {
                Write-Verbose "Check members '$($Identity.Name)'"
                $Past = Remove-ExcessHashItemsHC $Past ('Member' + ($Level+1))

                Get-ADGroupMember $Identity | ForEach-Object {
                    $Past.('Member' + ($Level+1)) = $_.Name

                    if ($CircularGroup = Test-CircularGroupMembershipHC $Past) {
                        $CircularGroup
                    }
                    else {
                        if (($_.ObjectClass -eq 'User') -or (-not (Get-ADGroupMember $_))) {
                            Write-Verbose "End node '$($_.Name)'"
                            [PSCustomObject]$Past
                        }
                        elseif ($_.ObjectClass -eq 'Group') {
                            Write-Verbose "Member group '$($_.Name)'"
                            Add-MemberGroupHC -Identity $_ -Past $Past -Level ($Level+1)
                        }
                    }
                }
            }
        }

        Function Remove-ExcessHashItemsHC {
            Param (
                [Parameter(Mandatory)]
                [hashtable]$HashTable,
                [Parameter(Mandatory)]
                [String]$Key
            )

            $OrderedHashTable = [Ordered]@{}

            $HashTable.GetEnumerator() | Sort-Object Name | where Name -LT $Key | ForEach-Object {
                $OrderedHashTable.($_.Name) = $_.Value
            }

            $OrderedHashTable
        }

        Function Test-CircularGroupMembershipHC {
            Param (
                [Parameter(Mandatory)]
                [HashTable]$HastTable
            )

            $HastTable.Values | Group-Object | where Count -GE 2 | ForEach-Object {
                Write-Warning "Circular group memberships found for group '$($_.Name)'"
                $Past[$Past.Count -1] = ($Past[-1] + ' *')
                [PSCustomOBject]$Past
            }
        }
    }

    Process {
        Try {
            $Past = [Ordered]@{}

            if ($Identity.GetType().Name -ne 'ADPrincipal') {
                $Identity = Get-ADGroup -Identity $Identity
            }

            Write-Verbose "Add property 'GroupName' value '$($Identity.Name)'"
            $Past.GroupName = $Identity.Name

            Add-MemberGroupHC -Identity $Identity -Past $Past -Level 0
        }
        Catch {
            throw "Failed retrieving members for group '$Identity': $_"
        }
    }
}