【问题标题】:Creating objects with unknown number of properties (powershell)创建具有未知数量属性的对象(powershell)
【发布时间】:2014-11-27 14:18:57
【问题描述】:

我正在编写一个 Powershell 脚本,该脚本将采用 OU(可选)、域名(默认为 company.local)和要返回的 AD 属性选择(默认为 name,lastlogondate)。我想将输出发送到 CSV 文件。

我有两个问题。

  1. 脚本返回从所有域控制器检索到的请求属性。我只想输入每个人的条目,它具有“LastLogonDate”的最新值。
  2. 我不知道如何处理使用随机键数构建属性哈希表。

对我应该如何处理这些问题有任何想法吗?谢谢。

这是我现在使用的代码:

[CmdletBinding()]
param(
    [string]$DomainName = "company.local",

    [string[]]$SearchPath = 'OU=people,DC=company,DC=local',

    [string[]]$OutputProperties = 'Name,lastlogondate'
)

Import-Module ActiveDirectory

$props = @{}

$temp = New-Object 'System.DirectoryServices.ActiveDirectory.DirectoryContext'("domain","$DomainName")
$dcs = [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($temp)

Foreach ($ou in $SearchPath) {
    $users = Get-ADUser -Filter * -SearchBase $ou -Properties $OutputProperties.Split(",") -Server $DomainName | Select $OutputProperties.Split(",")

    $time = 0

    Foreach ($dc in $dcs) {
        Foreach ($user in $users) {
            If ($user.LastLogonDate -gt $time) {
                $time = $user.LastLogonDate
            }
            $props.'LogonTime' = $time
            $props.'Name'=$user.Name
            New-Object Psobject -Property $props
        }
    }
} 

【问题讨论】:

  • 对于初学者,我猜您正在寻找检查所有域控制器上所有用户的详细信息? $users = Get-ADUser -Filter * .... 行是否应该不在 Foreach ($dc 循环内?您还必须将 -Server 更改为 -Server $dc。您对后期处理满意吗?获取所有详细信息,然后为每个用户找到最新的,然后过滤掉旧的?

标签: powershell active-directory


【解决方案1】:

我喜欢使用 Job 的想法,但在处理错误时遇到了麻烦。由于我们还有这么多 Server 2003 DC,我只是按照最初的想法去做。这是最终的脚本。感谢您的反馈。

<#
.Synopsis
    Searches ActiveDirectory and returns a user-specified list of properties
.DESCRIPTION 
    This script takes a user-specified list OUs and a user-specified list of desired properties.
.NOTES 
    Author: Mike Hashemi
    V1 date: 15 August 2014
    V2 date: 6 October 2014
        - Converted the main part of the script, into a function.
        - Added routie to gather all DCs in a domain, for the ability to return LastLogonDate.
.LINK
    http://stackoverflow.com/questions/26163437/creating-objects-with-unknown-number-of-properties-powershell
.PARAMETER DomainName
    Default value is 'company.local'. This parameter represents the DNS domain name, of the domain.
.PARAMETER SearchPath
    Default value is 'OU=people,DC=company,DC=local'. This parameter represents a comma-separated list of OUs to search.
.PARAMETER OutputProperties
    Default value is 'Name,Enabled,LastLogonDate'. This parameter represents a comma-separated list of properties to return.
.EXAMPLE
    .\get-ADUserProperties-Parameterized.ps1
    This example get's a list of all users in 'OU=people,DC=company,DC=local' and outputs the Name, Enabled, and LostLogonDate attributes.
.EXAMPLE
    .\get-ADUserProperties-Parameterized.ps1 -SearchPath 'OU=people,DC=company,DC=local','OU=managers,DC=company,DC=local'
    This example get's a list of all users in the 'OU=people,DC=company,DC=local' and 'OU=managers,DC=company,DC=local' OUs and outputs the 
    Name, Enabled, and LostLogonDate attributes.
.EXAMPLE
    .\get-ADUserProperties-Parameterized.ps1 -SearchPath 'OU=people,DC=company,DC=local' -OutputProperties Name,telephoneNumber | Export-CSV c:\users.csv -NoTypeInformation
    This example get's a list of all users in the 'OU=people,DC=company,DC=local' OU and outputs the Name and Telephone Number attributes. 
    The output is exported to a CSV.
#>
[CmdletBinding()]
param(
    [string]$DomainName = 'managed.local',

    [string[]]$SearchPath = 'OU=people,DC=company,DC=local',

    [string[]]$OutputProperties = 'Name,Enabled,LastLogonDate'
)

Function Get-TheUsers {
    #Create the hash table, for later.
    $props = @{}

    Try {
        #The next two lines get the list of domain controllers, using the supplied DNS domain name.
        Write-Verbose ("Getting domain controllers from {0}" -f $DomainName)
        $temp = New-Object 'System.DirectoryServices.ActiveDirectory.DirectoryContext'("domain","$DomainName")
        $dcs = [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($temp)
    }
    Catch [System.Management.Automation.MethodInvocationException] {
        Write-Error ("Unable to connect to remote domains. Please run the script from a DC in {0}. " -f $DomainName)
        Exit
    }
    Catch {
        Write-Error ("There was an unexpected error. The message is: {0}" -f $_.Exception.Message)
        Exit
    }

    Foreach ($ou in $SearchPath) {
        Write-Verbose ("Getting users in {0}" -f $ou)
        Foreach ($dc in $dcs) {
            If ($dc.OSVersion -like '*2003*') {
                Write-Warning ("Skipping {0}, because it is not a Server 2008 (or higher) DC." -f $dc)
            }
            Else {
                Write-Verbose ("Searching {0} on {1}." -f $ou,$dc)
                Try {
                    $users = Get-ADUser -Filter * -SearchBase $ou -Properties $OutputProperties.Split(",") -Server $dc -ErrorAction Stop | Select $OutputProperties.Split(",")
                }
                Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
                    Write-Error ("Unable to search {0}. It appears to be a non-existent OU. The specific error message is: {1}" -f $ou, $_.Exception.Message)
                    Exit
                }

                Foreach ($user in $users) {
                    ForEach($property in $OutputProperties.Split(",")) {
                        $props.$property = $user.$property
                    }
                    New-Object Psobject -Property $props
                }
            }
        }
    }
}

Try { 
    Import-Module ActiveDirectory -ErrorAction Stop
}
Catch [System.IO.FileNotFoundException] {
    Write-Error ("Unable to load the required module. The specific message is: {0}" -f $_.Exception.Message)
    Exit
}

$data = Get-TheUsers

#Takes the output of the Get-ADUser query and groups by the first property in $OutputProperties, then uses the LastLogonDate property (if present)
#to sort again and select only the last (most recent) entry.
Write-Verbose ("Sorting data.")
$data | Group-Object Name | ForEach-Object {$_.Group | Sort-Object LogonTimeDate | Select-Object -Last 1}

【讨论】:

    【解决方案2】:

    此时我不愿意删除我的其他答案。使用@TheMadTechincian 评论中的两位,我想看看我是否可以在使用工作时改进这一点。这是我第一次使用Start-Job,但我认为它可以解决问题。

    [CmdletBinding()]
    param(
        [string]$DomainName = "domain.local",
        [string[]]$SearchPath = 'OU=container,dc=domain,dc=local',
        [string[]]$OutputProperties = @("Name","lastlogondate","samaccountname")
    )
    
    Import-Module ActiveDirectory
    
    If(!($OutputProperties -contains "lastlogondate")){$OutputProperties += "lastlogondate"} 
    $temp = New-Object 'System.DirectoryServices.ActiveDirectory.DirectoryContext'("domain","$DomainName")
    $dcs = [System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($temp)
    
    $dcs | ForEach-Object{
        ForEach($singleOU in $SearchPath){
            $arguments = $singleOU,$OutputProperties,$_
            [void](Start-Job -ScriptBlock {
                    Get-ADUser -Filter * -SearchBase $args[0] -Properties $args[1] -Server $args[2]
                    } -ArgumentList $arguments)
        }
    }
    
    While((Get-Job -State 'Running').Count)
    {
        Start-Sleep -Milliseconds 200
    } 
    
    Get-Job | Receive-Job| Group-Object Name | ForEach-Object{$_.Group | Sort-Object lastlogondate | Select-Object $OutputProperties -Last 1}
    

    在每个 dc 上为每个 ou 搜索开始一个工作。这很容易失控。如果这是一个问题,我会参考一个帖子来限制所做的工作数量here。等待所有作业完成,然后处理输出,为每个用户查找最新的lastlogondate。将$OutputProperties 转换为数组不再需要拆分并将其用于所有选择,无需担心动态属性,因为它们将始终在输出中。由于脚本的最后一部分需要lastlogondate,因此我添加了一个 if 语句,以防用户不使用它。

    【讨论】:

    • @themadtechnician 这看起来适合使用这些工作吗?
    【解决方案3】:

    假设目的是正当的,扩展我的评论。我们可以使用一些简单的 cmdlet 对数据进行后期处理,而不是尝试确定循环内的最新时间。我认为你现在这样做的方式是比较所有用户之间的时间,这不是你想要的。我介绍以下内容。这段代码上面的所有内容我都保持不变(为了测试我编辑了params)

    $data = Foreach ($ou in $SearchPath) {
    
        Foreach ($dc in $dcs) {
    
        $users = Get-ADUser -Filter * -SearchBase $ou -Properties $OutputProperties.Split(",") -Server $dc | Select $OutputProperties.Split(",")
    
            Foreach ($user in $users) {
                $props.'LogonTime' = $user.LastLogonDate
                $props.'Domain Controller' = $dc
                $props.'Name'=$user.Name
                New-Object Psobject -Property $props
            }
        }
    }
    $data | Group-Object Name | ForEach-Object{$_.Group | Sort-Object LogonTime | Select-Object -Last 1}
    

    我将Get-ADUser 移动到 dc 循环内,以便我们查询所有请求用户的所有 dc(因为 LastLogon 时间戳可能不同)。我删除了 If 语句和对 $time 的引用,因为我们将在之后进行处理。你可以把它拿出来,但我为Domain Controller 添加了一个属性用于测试,因为我很好奇。将这些结果捕获到变量$data 中。每个用户使用$data 组。对于每个组,对登录时间进行排序,然后选择最近的登录时间。最后一行来自这个 SO question

    动态字段

    好的,所以你有 $OutputProperties 可以包含任何东西。无需担心包含的内容。在之前的Select-Object 中,您拥有所需的属性。只需回显$user,它将分配给$data,然后进行排序。

    Foreach ($user in $users) {
        $user
    }
    

    我知道这很多,我会更新答案以不那么冗长,但我想在重做之前结合 TheMadTechnician 的想法。我看的越多,我认为我可以改变的越多。我现在让它更有效率。

    【讨论】:

    • 为了速度,我建议为每个服务器运行 Get-ADUser 以作为作业运行,然后您可以使用 Do/While((Get-Job).count -gt 0 ) 循环以检索结果并创建用户数组以进行处理。这样,它就不会在查询下一个服务器之前坐在那里等待每台服务器。只是我的两个位。
    • @TheMadTechnician 不要破旧。无论如何,这就是我从其他人的想法和建设性批评中学习的方式。将尝试使这项工作和更新。谢谢。
    • 好主意,他们解决了第一个问题。我添加了一些代码来跳过 Server 2003 DC(因为它们没有 ADWS)。如何在不知道用户要检索哪些属性的情况下创建包含属性和值的哈希表?
    • 换句话说,如何循环访问 $OutputProperties 中的值以创建以 $user.something 作为值的哈希表?我知道我可以使用 $props.add() 添加键,但不知道如何添加值。
    • $OutputProperties 会动态变化吗?您希望能够构建输出以反映正确吗?
    猜你喜欢
    • 2018-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-03
    相关资源
    最近更新 更多