I did some PowerShell script to find inactive users in AD that are 90 days old looping through all DCs to also get LastLogon attribute. I also need some extra attributes that only ADUser can bring out. I've got an error when running my script with the piping in the $users = Search-ADAccount line.
Import-Module ActiveDirectory
function Get-ADUsersLastLogon() {
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$OUs = #()
$OU += "ou=Users-A,ou=Users,ou=Items,dc=mydc,dc=com"
$OU += "ou=Users-B,ou=Users,ou=Items,dc=mydc,dc=com"
$time = 0
$exportFilePath = "c:\tmp\lastLogon-test $(get-date -f dd-MM-yyyy).csv"
$columns = "name;username;whencreated;whenchanged;DNname;datetime"
#InactiveTest
$InactiveFilter = #{
UsersOnly = $true
AccountInactive = $true
TimeSpan = New-Timespan -Days 90
}
#EndInactiveTest
Out-File -FilePath $exportFilePath -Force -InputObject $columns
foreach ($OU in $OUs) {
$users = Search-ADAccount #InactiveFilter |
Get-ADUser -Filter * -SearchBase $OUs -Property displayName, whenCreated, whenChanged
foreach ($user in $users) {
foreach($dc in $dcs) {
$hostname = $dc.HostName
$currentUser = Get-ADUser $user.SamAccountName |
Get-ADObject -Server $hostname -Properties lastLogon
if ($currentUser.LastLogon -gt $time) {
$time = $currentUser.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
$row = $user.displayName + ";" + $user.SamAccountName + ";" +
$user.whenCreated + ";" + $user.whenChanged + ";" +
$user.distinguishedName + ";" + $dt
Out-File -FilePath $exportFilePath -Append -NoClobber -InputObject $row
$time = 0
}
}
}
Get-ADUsersLastLogon
I think iterating through DC's and OU's and then collecting only the inactive users last logon dates could best be done using a Hashtable object as intermediate storage.
This helps avoiding duplicate entries and gives the opportunity to compare the LastLogonDate properties.
For the final output, it uses one single cmdlet called Export-Csv.
Below my (untested) code:
function Get-ADUsersLastLogon {
# get your ad domain
$DomainName = (Get-ADDomain).DNSRoot
# get all DC hostnames as string array
$DCs = Get-ADDomainController -Filter * -Server $DomainName | Select-Object -ExpandProperty Hostname
# create an array of OU distinghuished names used as SearchBase
$OUs = "OU=Users-A,OU=Users,OU=Items,DC=mydc,DC=com", "OU=Users-B,OU=Users,OU=Items,DC=mydc,DC=com"
$exportFilePath = "c:\tmp\lastLogon-test $(Get-Date -Format dd-MM-yyyy).csv"
$InactiveFilter = #{
UsersOnly = $true
AccountInactive = $true
TimeSpan = New-Timespan -Days 90
}
# use a lookup Hashtable to eliminate duplicates and collect only the latest logon dates
$lookup = #{}
# loop through the list of dc's
foreach ($dc in $DCs) {
# loop through the list of OU's
foreach ($ou in $OUs) {
$users = Search-ADAccount #InactiveFilter -SearchBase $ou -Server $dc
foreach($user in $users) {
# get the properties we want from the AD User.
# using the PowerShell property names, we get the dates already converted into DateTime objects.
$usr = Get-ADUser -Identity $user.DistinguishedName -Server $dc -Properties DisplayName, Created, Modified, LastLogonDate |
Select-Object #{Name = 'Name'; Expression = {$_.DisplayName}},
SamAccountName,
#{Name = 'WhenCreated'; Expression = {$_.Created}},
#{Name = 'WhenChanged'; Expression = {$_.Modified}},
#{Name = 'DistinguishedName'; Expression = {$_.DistinguishedName}},
#{Name = 'LastLogon'; Expression = {$_.LastLogonDate}}
if ($usr) {
if ($lookup.ContainsKey($($user.DistinguishedName))) {
# we have collected this user before
$lastLogon = $lookup[$($user.DistinguishedName)].LastLogon
if ($lastLogon) {
if (($usr.LastLogon) -and $lastLogon -lt $usr.LastLogon) {
# only store this new instance if the $user.LastLogon property is of a later date
$lookup[$($user.DistinguishedName)] = $usr
}
}
}
else {
# this is a new user, so add the object to the HashTable
$lookup[$($user.DistinguishedName)] = $usr
}
}
else {
# should never happen..
Write-Warning "User $($user.SamAccountName) not found."
}
}
}
}
# export the objects contained in the $lookup Hashtable as CSV
($output = foreach ($key in $lookup.Keys) {
$lookup.$key
}) | Export-Csv -Path $exportFilePath -NoTypeInformation -Delimiter ';' -Encoding UTF8 -Force
}
Hope that helps
#voilier Sorry, I don't understand how it works for you. Pasted your code and Get-ADUser cmdlet expects filter value. If you use get-help get-aduser -full you will see that searchbase parameter can only be used with Filter or LDAPFilter parameters. More than that neither of them accept pipeline input. Identity parameter accepts pipeline input by value only. so you need to use the distinguishedname property from Search-ADAccount #InactiveFilter for example distinguishedname and pass it to filter
$users = Search-ADAccount #InactiveFilter | %{Get-ADUser -filter {distinguishedname -eq $_.distinguishedname} -SearchBase $OU -Property displayName, whenCreated, whenChanged}
I replaced your $users=... part with the code above and now I see no errors and CSV file created successfully.
Replace your foreach $ou in $ous with this and check the csv file. it works on my computer
Foreach ($ou in $ous){
$users = (Search-ADAccount #InactiveFilter | %{Get-ADUser -filter {distinguishedname -eq $_.distinguishedname} -SearchBase $OU -Property displayName, whenCreated, whenChanged})
foreach ($user in $users) {
foreach($dc in $dcs) {
$hostname = $dc.Name
$last_logon_time=((Get-ADUser $user.SamAccountName | Get-ADObject -Server "$hostname" -Properties lastLogon) |?{$_.lastlogon -gt $time}) | select -ExpandProperty lastlogon
}
$dt = [DateTime]::FromFileTime("$last_logon_time")
$row = $user.displayName + ";" + $user.SamAccountName + ";" +
$user.whenCreated + ";" + $user.whenChanged + ";" +
$user.distinguishedName + ";" + $dt
Out-File -FilePath $exportFilePath -Append -NoClobber -InputObject $row
$last_logon_time = 0
}
}
I hope it helps you
Related
I have a PS script that loops AD to get lastlogon attribute plus others.
I want to get the displayName, SamAccountName, whenCreated, whenChanged, distinguishedname and last logontime.
Now it all works fine except the output CSV is missing the displayName, whenCreated and whenChanged attributes.
I'm puzzeled that it works for some attributes and not others.
Here's my code:
Import-Module ActiveDirectory
function Get-ADUsersLastLogon() {
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$users = Get-ADUser -Filter * -SearchBase "ou=Other Users,ou=Users,ou=Items,dc=mydc,dc=,dc=com"
$time = 0
$exportFilePath = "c:\tmp\lastLogon $(get-date -f dd-MM-yyyy).csv"
$columns = "name;username;whencreated;whenchanged;DNname;datetime"
Out-File -FilePath $exportFilePath -Force -InputObject $columns
foreach ($user in $users) {
foreach ($dc in $dcs) {
$hostname = $dc.HostName
$currentUser = Get-ADUser $user.SamAccountName |
Get-ADObject -Server $hostname -Properties lastLogon
if ($currentUser.LastLogon -gt $time) {
$time = $currentUser.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
$row = $user.displayName + ";" + $user.SamAccountName + ";" +
$user.whenCreated + ";" + $user.whenChanged + ";" +
$user.distinguishedName + ";" + $dt
Out-File -FilePath $exportFilePath -Append -NoClobber -InputObject $row
$time = 0
}
}
Get-ADUsersLastLogon
I have a script from a previously answered question, but don't have enough reputation to comment. I tried to run that script and came across this error message:
Export-CSV : Cannot append CSV content to the following file: C:\users.csv. The appended object does not have a property that corresponds to the following column: User;Group. To continue with mismatched properties, add the -Force parameter, and then retry the command.
How can I debug this script to resolve this issue?
Function Get-ADGroupsRecursive{
Param([String[]]$Groups)
Begin{
$Results = #()
}
Process{
ForEach($Group in $Groups){
$Results+=$Group
ForEach($Object in (Get-ADGroupMember $Group|?{$_.objectClass -eq "Group"})){
$Results += Get-ADGroupsRecursive $Object
}
}
}
End{
$Results | Select -Unique
}}
import-module activedirectory
$users = get-aduser -Filter {Name -Like "*"} -Searchbase "OU=Sample Accounts,DC=domain,DC=com" -Properties MemberOf | Where-Object { $_.Enabled -eq 'True' }
$targetFile = "C:\users.csv"
rm $targetFile
Add-Content $targetFile "User;Group"
foreach ($user in $users)
{
$Groups = $User.MemberOf
$Groups += $Groups | %{Get-ADGroupsRecursive $_}
$Groups | %{New-Object PSObject -Property #{User=$User;Group=$_}}|Export-CSV $targetfile -notype -append
}
try this function
function Get-InChainGroups
{
param (
[parameter(mandatory = $true)]
$user,
$domain)
$user1 = (get-aduser -filter { name -eq $user } -server $domain).distinguishedname
Write-verbose "checking $user"
$ldap = "(&(objectcategory=group)(groupType:1.2.840.113556.1.4.803:=2147483648)(member:1.2.840.113556.1.4.1941:=$user1))"
try { Get-ADobject -LDAPFilter $ldap -server $domain | select #{ n = 'Identity'; e = { $user } }, Name, #{ n = 'DN'; e = { $_.distinguishedname } } | ft -a }
catch { "Exception occurred" }
}
i want to export csv file with columns "ParentGroupName", "MemberName", "DisplayName"
At the moment it is exporting the three datas into one column.
function getGroups{
$Groups += Get-ADGroup -Filter * -SearchBase "ou=Groups,ou=DCM,ou=NTG,dc=prod,dc=main,dc=ntgov" | Select-Object -ExpandProperty samaccountname
return $Groups
}
Measure-Command{
$Groups = getGroups
write-host "Groups:" $Groups.Count
}
Measure-Command{
$date = $(get-date).ToString("dd MMM yyyy")
$global:FileName = "Active Directory Group Membership - DCM -" + $date
$stringBuilder = New-Object System.Text.StringBuilder
foreach ($GroupName in $Groups){
Get-ADGroupMember -Identity $GroupName | Sort-Object $_.SamAccountName | ForEach-Object {
$ParentGroupName = (Get-ADGroup -Identity $GroupName).SamAccountName
$MemberName = $_.SamAccountName # Member of the Group.
if ($_.ObjectClass -eq 'group') {
$DisplayName = ' - '
} elseif ($_.ObjectClass -eq 'user') {
$a = (Get-ADUser -Identity $MemberName -Properties Displayname)
$DisplayName = $a.DisplayName
}
$null = $stringBuilder.Append("$ParentGroupName, $MemberName, $DisplayName")
}
}
outputArray = $stringBuilder.ToString()
out-file C:\Users\augut\Desktop\$FileName.csv
outputArray | out-file C:\Users\augut\Desktop\$FileName.csv
}
You're making a headache for yourself by manually constructing the CSV file. This can be simplified considerably by constructing a custom object for each item found with the properties you need recorded, then stuffing those into an array and exporting that array to a CSV file.
function getGroups{
$Groups += Get-ADGroup -Filter * -SearchBase "ou=Groups,ou=DCM,ou=NTG,dc=prod,dc=main,dc=ntgov" | Select-Object -ExpandProperty samaccountname
return $Groups
}
$Groups = getGroups
write-host "Groups:" $Groups.Count
$date = $(get-date).ToString("dd MMM yyyy")
$global:FileName = "Active Directory Group Membership - DCM -" + $date
$results = #();
foreach ($GroupName in $Groups){
Get-ADGroupMember -Identity $GroupName | Sort-Object $_.SamAccountName | ForEach-Object {
$ItemProperties = #{
"ParentGroupName" = (Get-ADGroup -Identity $GroupName).SamAccountName;
"MemberName" = $_.SamAccountName
}
if ($_.ObjectClass -eq 'group') {
$ItemProperties.Add("DisplayName","-");
} elseif ($_.ObjectClass -eq 'user') {
$ItemProperties.Add("DisplayName",(Get-ADUser -Identity $MemberName -Properties DisplayName).DisplayName);
}
$MyItem = New-Object -TypeName psobject -property $ItemProperties;
$Results += $MyItem;
$ItemProperties = $null;
}
}
$results | export-csv -path "C:\Users\augut\Desktop\$FileName.csv" -NoTypeInformation
I put together the below, which does the job. However, the output isn't very workable. So I wanted to output this all to a CSV using Export-Csv. Im aware I can do this by moving to a ForEach-Object query, but im not entirely sure how to achieve that.
I have added an attempt to convert it in hopes of a little help. I'm not sure how to specify the variable for each object. For example the first section calls all domains in the forest. How do i use each response in the next piped query? and so on.
$domains = (Get-ADForest).Domains
$controllers = #()
$worked = $false
foreach ($domain in $domains) {
$controller = Get-ADDomainController -Discover -ForceDiscover -DomainName $domain |
Select-Object HostName
$controllers += $controller
}
while (-not $worked) {
try {
foreach ($item in $controllers) {
$value = $item.HostName.Value
Write-Host $value
Write-Host 'Domain Admins'
Get-ADGroupMember -Identity 'Domain Admins' -Server $value |
Get-ADUser -Properties name, samaccountname, Description, EmailAddress |
Where {$_.Enabled -eq $true} |
Format-Table Name, SamAccountName, Description, EmailAddress -AutoSize
}
$worked = $true
} catch {}
}
Conversion Attempt
ForEach-Object{
(Get-ADForest).domains | Get-ADDomainController -Discover -ForceDiscover -DomainName $domain |Select-Object HostName | Get-ADGroupMember -identity 'Domain Admins' -Server $value | Get-ADUser -Properties samaccountname, Description, EmailAddress | Where {$_.Enabled -eq $true}
}| Export-Csv -Path "$HOME/Desktop/DomainAdmins.csv" samaccountname, Description, EmailAddress -AutoSize
If you can get the values from your Get-ADUser call and put them in an object, you can then pipe to convertto-csv.
Here's an example:
$arr = #([pscustomobject]#{name="name"; sam="samaccountname"}, [pscustomobject]#{name="name2"; sam="samaccountname2"});
$arr | ConvertTo-Csv -NoTypeInformation
"name","sam"
"name","samaccountname"
"name2","samaccountname2"
You could get rid of the Format-Table call. The code I've shown in the example pipes and array of objects into the convertto-csv cmdlet. So if Get-ADUser returns objects, you should be able to pipe right into ConvertTo-CSV or Export-Csv -append
The objects are hashtables that are cast to pscustomobjects, it's a nice quick way to illustrate the technique.
The result, as shown, will be csv headers that match your hashtable keys, and the hastable values will be the CSV values.
This is working fine in my local environment and storing the result in D:\Test_File.csv
$domains = (Get-ADForest).Domains
$controllers = #()
$worked = $false
foreach ($domain in $domains) {
$controller = Get-ADDomainController -Discover -ForceDiscover -DomainName $domain | Select-Object HostName
$controllers += $controller
}
while (-not $worked) {
try
{
foreach ($item in $controllers)
{
$value = $item.HostName.Value
Write-Host $value
Write-Host 'Domain Admins'
Get-ADGroupMember -Identity 'Domain Admins' -Server $value |
Get-ADUser -Properties name, samaccountname, Description, EmailAddress |?{$_.Enabled -eq $true}|Export-Csv -Append "D:\Test_File.csv"
}
#$worked = $true
}
catch
{
$Error_Message=$_.Exception.Message
}
}
I have a task to get userPrincipalName attribute from users who are in several groups in our multiple-domain AD forest.
The problem is that I can't use Select-Object to get a user's UPN from Get-ADGroupMember because this cmdlet only returns a limited number of properties (samaccountname, name, SID and DN), and UPN isn't one of them.
I wrote this code (get "name" and than search UPN by "name"):
$ScriptPath = Split-Path $MyInvocation.MyCommand.Path
$LocalSite = (Get-ADDomainController -Discover).Site
$NewTargetGC = Get-ADDomainController -Discover -Service 6 -SiteName
$LocalSite
IF (!$NewTargetGC)
{ $NewTargetGC = Get-ADDomainController -Discover -Service 6 -NextClosestSite }
$NewTargetGCHostName = $NewTargetGC.HostName
$LocalGC = “$NewTargetGCHostName” + “:3268”
$domains = (Get-ADForest).domains
$MembersOfSFDC_Groups = foreach ($domain in $domains) {
$Group = Get-ADGroup -Filter { Name -like "*groupname*" } -Server $Domain
$Group | Get-ADGroupMember -Server $domain | Select #{
Name="Domain";Expression={$Domain}},#{
Name="Group";Expression={$Group.Name}}, name}
$DisplayNames = $MembersOfSFDC_Groups.name
$DisplayNames |Out-file (Join-Path $ScriptPath 'DisplayNames.txt')
Get-content (Join-Path $ScriptPath 'DisplayNames.txt') |
$displaynames | ForEach-Object {
Get-ADUser -Server $LocalGC -Filter {Name -eq $_} |
Select-Object -Property userPrincipalName} |
Out-File (Join-Path $ScriptPath 'upnOfSDFC_AD_GroupsMembers.txt')
But next problem is that this code is running about 30 min (Measure-Command cmdlet). We have a huge number of users across multiple domains.
My question is how to improve my code to get user's UPN more faster?
I know about System.DirectoryServices.DirectorySearcher, but don't know how to implementing this method with my txt-file (list of "names").
Any help will be much appreciated.
You can actually get it from one line of code. Simples... :)
Get-ADGroupMember -Identity "group name" |%{get-aduser $_.SamAccountName | select userPrincipalName } > c:\scripts\upnofADgroup.txt
Fastest approach is probably avoiding Get-ADGroupMember altogether, and just search for the group, and then search for objects that are members of that group:
$Group = Get-ADGroup -Filter { Name -like "*groupname*" } -Server $Domain
$Members = Get-ADObject -LDAPFilter "(memberOf=$($Group.DistinguishedName))" -Properties UserPrincipalName
$Members |Select-Object UserPrincipalName |Out-File (Join-Path $ScriptPath 'upnOfSDFC_AD_GroupsMembers.txt')
Now you're down to 2 queries, rather than 2 + N (where N is the number of members)
Ok, guys, I'v got it:
function Get-DomainFromDN ($param)
{
$dn1 = $param -split "," | ? {$_ -like "DC=*"}
$dn2 = $dn1 -join "." -replace ("DC=", "")
$script:test = $dn2
return $dn2
}
foreach ($Group in $Groups) {
$Members = Get-ADObject -LDAPFilter "(&(objectCategory=user)(memberOf=$($Group.DistinguishedName)))" -Properties UserPrincipalName -Server (Get-DomainFromDN ($group.DistinguishedName))
$UPN_Of_SFDC_Groups += $Members |Select-Object UserPrincipalName }
$UPN_Of_SFDC_Groups | Out-file (Join-Path $ScriptPath 'upnOfSDFC_AD_GroupsMembers.txt')