【问题标题】:Recursive function not behaving correctly递归函数行为不正确
【发布时间】:2017-05-24 12:11:21
【问题描述】:

我们在组成员的活动目录中具有以下结构:

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 *

在没有孩子的情况下调用自己时,怎么可能只输出最深的层次?

【问题讨论】:

  • 您当前的输出是什么?另外,我认为您缺少 else 或在倒数第二个 if 块中有一个额外的括号。可能是复制粘贴错误?

标签: powershell recursion


【解决方案1】:

我想从一开始就采用自己的方法来了解其工作原理。我不能说这是否比您预期的方法更好或更差。希望您可以使用它来查看您可能出错的地方。

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 并沿管道发送。

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


样本输出

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 块,并进入高级功能但现在可以正常运行。 谨防循环组,因为没有逻辑可以检测到这些。

【讨论】:

  • @DarkLite1 我添加了一些东西来弥补 PowerShell 在这件事上的行为。我现在看看你的函数,看看我能不能弄清楚发生了什么。
  • 谢谢马特!我总是想知道为什么你的代码看起来比我的更专业......我得赶上一段时间了。现在学习 Java 课程以弥补逻辑思维方面的一些差距。谢谢你,感激不尽:)
  • 感谢关于循环组的提示。为此,我将在代码中添加一些检查。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2021-12-11
    • 1970-01-01
    • 2012-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-01
    相关资源
    最近更新 更多