我正在开发一个Powershell脚本,该脚本将采用OU(可选),域名(默认为company.local)以及要返回的一系列AD属性(默认为name,lastlogondate)。我想将输出发送到CSV文件。
我有两个问题。
关于我应该如何处理这些问题的任何想法?感谢。
这是我现在正在使用的代码:
[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 :(得分:0)
在我的评论中,假设最终证明手段合理。我们可以使用一些简单的cmdlet来处理数据,而不是尝试确定循环内部的最新时间。我认为你现在这样做的方式是比较所有用户之间的时间,而不是你想要的。我提出以下内容。高于此代码的所有内容我都保持不变(对于测试我编辑了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循环中,以便我们查询所有请求用户的所有dc(因为LastLogon时间戳可能不同)。我删除了If
语句和$ time的引用,因为我们将要处理。你可以把它拿出去,但我为Domain Controller
添加了一个属性用于测试,因为我很好奇。将这些结果捕获到变量$data
中。每个用户选择$data
组。对于每个组,对登录时间进行排序,然后选择最后一个最近的登录时间。最后一行来自此SO question
动态字段
好吧,你有$OutputProperties
可以包含任何东西。没有必要担心包含的内容。在之前的Select-Object
中,您拥有所需的属性。只需回显$user
,它就会被分配到$data
然后进行排序。
Foreach ($user in $users) {
$user
}
我知道这很多,我会更新答案并不是那么冗长,但我想在重做之前加入TheMadTechnician的想法。我看得越多,我认为我可以改变的越多。我现在正在提高效率。
答案 1 :(得分:0)
我不习惯在此时删除我的其他答案。使用@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}
为每个直流上的每个搜索开始一项工作。这很容易失控。如果这是一个问题,我会提到限制作业量here的帖子。等待所有作业完成,然后处理输出,查找每个用户的最新lastlogondate
。将$OutputProperties
转换为数组会停止对拆分的需求并将其用于所有选择,从而无需担心动态属性,因为它们始终位于输出中。由于脚本的最后一部分需要lastlogondate
,我添加了一个if语句,以便在用户不使用它时添加它。
答案 2 :(得分:0)
我喜欢使用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}