递归函数行为不正常

时间:2017-05-24 12:11:22

标签: powershell recursion

我们在活动目录中为组成员身份提供以下结构:

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 *

如果在没有孩子的时候打电话给自己,它怎么能输出最深的水平呢?

2 个答案:

答案 0 :(得分:2)

我想采用自己的方法从一开始就了解它的工作原理。我不能说这比你想要的方法好还是坏。希望您可以使用它来查看您可能出错的地方。

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语句。

样本输出

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

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

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

不使用Format-List,就像你完成的那样输出可能看起来不正确,因为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
} 

这将创建一个馈送到Select-Object的已排序属性列表。在创建哈希表时,使用[ordered]可能看起来更聪明/更容易,但是你无法克隆有序哈希,所以这就是我绕过它的方式。

这里的所有功能都可以变得更加健壮,即使用像你一样的begin块,进入高级功能,但现在功能正常。 小心循环组,因为没有逻辑来检测它们。

答案 1 :(得分:0)

我只想发布我最终修复错误代码的方法。感谢@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': $_"
        }
    }
}