递归函数行为不正确 [英] Recursive function not behaving correctly

查看:61
本文介绍了递归函数行为不正确的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们在活动目录中为群组成员设置了以下结构:

We have the following structure in active directory for group memberships:

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] 作为输出.我不是递归函数方面的专家,但我编写的以下代码非常接近:

So for every deepest object there needs to be a [PSCustomObject] as output. I'm not such an expert in recursive functions, but the following code I made comes very close:

$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 *

在没有孩子的情况下,如何让它在调用自己时只输出最深的级别?

How is it possible to have it output only the deepest levels when calling itself when there are no children anymore?

推荐答案

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

I wanted to take my own approach to learn how this works from the start. I cannot say if this is better or worse than your intended approach. Hopefully you can use this to see where you might have gone wrong.

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 并通过管道发送.

What we do here is build hashtables that are passed recursively to the function. When a group is encountered the function is called again. If there is ever a group with 0 members or if a user is found the chain thus far is converted to a psobject and sent down the pipe.

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

There is one small side effect with this as you cannot guarentee the order that the members get displayed. You will see to build your own select statement for that if this is an issue.

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 将根据管道中的第一个对象进行显示,但所有属性都将在那里.如果这是一个问题,那么您需要创建一个小函数来保证属性输出的顺序.一个基本的例子是:

Without using Format-List, like you have done the output might not appear correct, as PowerShell will display based on the first object in the pipe, but all of the properties will be there. If this is an issue then you need to create a small function to guarantee the order of property output. A basic example would be:

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] 似乎更聪明/更容易,但您不能克隆有序哈希,所以这就是我解决它的方法.

That will create a sorted property list that is fed to Select-Object. It might seem smarter / easier to use [ordered] when creating the hashtable but you cannot clone an ordered hash so this is the way I got around it.

这里的所有函数都可以变得更健壮,即像你一样使用 begin 块,并进入高级函数,但现在可以正常运行.当心循环组,因为没有逻辑可以检测到这些.

All of the functions here could be made more robust, i.e. use the begin block like you have, and into advanced functions but function correctly now. Beware of circular groups as there is no logic to detect those.

这篇关于递归函数行为不正确的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆