创建具有未知属性数量的对象(powershell)

问题描述:

我正在使用一个Powershell脚本,它将采用OU(可选),域名(默认为company.local)以及一组要返回的AD属性(默认是名字,lastlogondate)。我想将输出发送到一个CSV文件。创建具有未知属性数量的对象(powershell)

我有两个问题。

  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 
     } 
    } 
} 
+0

对于初学者,我猜你想查看所有域控制器上的所有用户的详细信息?如果'$ users = Get-ADUser -Filter * ....'行不在'Foreach($ dc'循环)中,你还必须将'-Server'更改为-'Server $ dc'。您对后处理感到满意吗?获取所有细节,然后为每个用户找到最新的一个,然后筛选出旧的? – Matt 2014-10-02 15:41:48

我喜欢使用Job的想法,但在处理错误时遇到了麻烦。由于我们仍然拥有如此多的Server 2003数据中心,我只是按照最初的想法去做。这是最后的脚本。感谢您的反馈。

<# 
.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} 

Exanding对我的评论假设不择手段。我们可以用一些简单的小命令来处理数据,而不是试图确定循环中的最新时间。我认为你现在这样做的方式是比较所有用户之间的时间,而不是你想要的时间。我介绍以下内容。上面这段代码的一切,我离开了相同的(为了测试我编辑的param S)

$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的所有要求的用户(因为它是可能的LastLogon时间戳可能不同)。我删除了If声明和对$ time的引用,因为我们将在后面处理。你可以把它拿出来,但我添加了一个Domain Controller的属性进行测试,因为我很好奇。将这些结果捕获到变量$data中。由每个用户以$data组。对于每个组,排序登录时间并选择最近的那个。最后一行来源于此SO question

动态字段

好了,所以你必须$OutputProperties可能包含任何数据。没有必要担心包含什么。在之前的Select-Object中,你有你想要的属性。只是回显$user,它将被分配到$data然后进行排序。

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

我知道这是很多,我会更新的答案并不像冗长,但我想结合TheMadTechnician的想法之前,我重做。我越看越这个,我认为我可以改变得越多。我正在使它更高效。

+0

为了提高速度,我建议为每台服务器运行Get-ADUser作为一项工作来运行,那么你可以使用Do/While((Get-Job).count -gt 0)循环来检索结果并创建你的用户数组进行处理。这样它就不会坐在那里等待每个服务器进行查询下一个,只是我的两个位 – TheMadTechnician 2014-10-02 16:25:03

+0

@TheMadTechnician不要破旧这就是我从其他人的想法和建设性的批评中学习的方法,将尝试做出这些工作并更新,谢谢 – Matt 2014-10-02 16:33:22

+0

好主意,他们解决了第一个问题。我添加了一些代码来跳过Server 2003 DCs(因为他们没有ADWS)。 如何在不知道用户要检索哪些属性的情况下使用属性和值创建散列表? – StackExchangeGuy 2014-10-02 19:19:10

我不适合在此时删除我的其他答案。使用@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语句,在用户没有使用它的情况下会添加它。

+0

@themadtechnician是否看起来像是对这些工作的适当使用? – Matt 2014-10-03 02:36:30