New-object, property "disappears" - powershell

I'm trying to make a script returning AD users ID, Display Name and Group memberships based on an input file and export the result to another file.
However, the group membership information seems to get lost in the process.
Any ideas?
My current script:
$Result = #()
ForEach ($_ in gc userlist.csv)
{
$User = Get-ADUser $_ -Properties DisplayName, SamAccountName, LastLogonDate | Select DisplayName, SamAccountName, LastLogonDate
$Groups = Get-ADPrincipalGroupMembership $_ | Select Name
# So far it seems to work
$Properties = #{
UserID = (#($User.SamAccountName) -join ',')
Name = (#($User.DisplayName) -join ',')
LastLogonDate = (#($User.LastLogonDate) -join ',')
Groups = (#($Groups.Name) -join ',')
}
# By this time, Groups doesn't return any information
$Result += New-Object psobject -Property $Properties
}
$Result | Select-Object Name, UserID, Groups, LastLogonDate | Export-Csv -NoTypeInformation -Path output.csv

Here's what I think might work:
$Result = #()
ForEach ($User in gc userlist.csv) {
$UserDetails = Get-ADUser $User -Properties DisplayName, SamAccountName, LastLogonDate | Select DisplayName, SamAccountName, LastLogonDate
$Groups = Get-ADPrincipalGroupMembership $User | Select Name
$Properties = #{
UserID = $UserDetails.SamAccountName
Name = $UserDetails.DisplayName
LastLogonDate = $UserDetails.LastLogonDate
Groups = ($Groups.Name -join '; ')
}
$Result += New-Object psobject -Property $Properties
}
$Result | Select-Object Name, UserID, Groups, LastLogonDate | Export-Csv -NoTypeInformation -Path output.csv
Per my comment, although using $_ will probably work its not particularly good practice to manually set that variable so i've changed it to $user.
I've therefore changed your $user to $userdetails.
I removed your casting as an array #() for each of the properties as well as your joining them with ,. I'm not sure why this is necessary, except perhaps with the Groups property, but in order to then not confuse the CSV result, i've joined these with a ; instead.

Related

Exporting certain users' samAccountName from AD to csv with only EmployeeNumbers known

I am trying to export certain users' SamAccountName and EmployeeNumber from AD to csv. I only have EmployeeNumbers from HR in csv file and have to match it to SamAccountName in AD. My code does not work past 'if' condition. When I echo output there are same values for $a and $b all in String type. This file C:\temp\UsersToDisable.csv contains 4 and 3 number with column name "EmployeeNumber". This is what i came up with:
Import-Module ActiveDirectory
$Nums = Import-Csv "C:\powershell\EmployeeNumbers.csv"
$Users = Get-ADUser -Filter "*" -Property EmployeeNumber -SearchBase
"DC=my,DC=example,DC=com" | Where { $_.EmployeeNumber -ne $null } |
Select SamAccountName,EmployeeNumber
Foreach ($user in $Users)
{
$EmployeeNumber = $user.EmployeeNumber
foreach ($Line in $Nums)
{
$number = $line.EmployeeNumber
$a = $number.toString()
$b = $EmployeeNumber.toString()
echo $a $b
if($a -eq $b)
{
echo $user.SamAccountName
$result += ,(Get-ADUser $user.SamAccountName -Properties * | Select SamAccountName,employeeNumber)
}
}
} $result | Export-CSV "C:\temp\CCI_All_Users.csv" -NoTypeInformation
Thank you for any advice!
Loading all the users from the directory and then searching once again for the same user in the directory is not an ideal solution for performance reason.
Try the following code. For each number from the input file, search in the directory the user having the current EmployeeNumber, requesting to load the EmployeeNumber property. Then select only the desired properties and export them to CSV.
Import-Module ActiveDirectory
$Nums = Import-Csv "C:\powershell\EmployeeNumbers.csv"
$Nums | ForEach-Object {
Get-ADUser -LdapFilter "(EmployeeNumber=$_)" -Property EmployeeNumber -SearchBase "DC=my,DC=example,DC=com" |
Select-Object SamAccountName, EmployeeNumber
} | Export-CSV "C:\temp\CCI_All_Users.csv"
An alternative to Hazrelle's helpful answer, very similar but instead of looping over each line of the CSV, taking advantage of the LDAPFilter capabilities with the help of some string manipulation:
# This assumes there is a column named "EmployeeNumber" in the CSV
$Nums = (Import-Csv "C:\powershell\EmployeeNumbers.csv").EmployeeNumber
# Assuming Nums is an Array with Employee Numbers. i.e.:
# $Nums => A123,A456,A789
# $filter would look like this:
# (|(employeenumber=A123)(employeenumber=A456)(employeenumber=A789))
$filter = [string]::Format(
'(|(employeenumber={0}))',
($Nums -join ')(employeenumber=')
)
$params = #{
Properties = 'EmployeeNumber'
SearchBase = 'DC=my,DC=example,DC=com'
LDAPFilter = $filter
}
Get-ADUser #params | Select-Object samAccountName, EmployeeNumber |
Export-CSV "C:\temp\CCI_All_Users.csv" -NoTypeInformation
Worth mentioning, $result += ,(Get... is not recommended.

Export users & groups & if they are a member of group

I need to do a script that will export me enabled users from specific OU and specific value of attribute Company (with some other attributes) and all groups beginning with name "A_ACC*" and if the user is a member of a group or no.
It should look like this on image. Desired result
For now I am working with this:
Get-ADUser -Filter {Company -eq "xxxx"} -SearchBase “yyyyy” -Properties GivenName, Surname, DisplayName, Company, Department, memberof, SamAccountName, PhysicalDeliveryOfficeName | Where {($_.Enabled -eq $True)} | % {
New-Object PSObject -Property #{
SamAccountName= $_.SamAccountName
GivenName = $_.GivenName
Surname=$_.Surname
DisplayName = $_.DisplayName
Company=$_.Company
Department = $_.Department
Location = $_.PhysicalDeliveryOfficeName
Groups = ($_.memberof | Get-ADGroup | Where Name -like "A_ACC*" | Select -ExpandProperty Name) -join ", "
}
} | Select GivenName, Surname, DisplayName, Company, Department, Location, Groups | Export-Csv -Path 'H:\My Documents\PowerShell_exports\Users_export.csv' -NoTypeInformation -Encoding UTF8
But unfortunately I do not know how to proceed further to get result I want.
Can you please advise?
Thank you!
If you have a list of groups that you are looking for you can enters these into an array (shown below as $groupsToCheckFor). Then add a property to each user object for each group with either true/false value depending on whether the group is found in the user's list of groups from memberof
$params = #{
Filter = { Company -eq 'xxxx' }
SearchBase = 'yyyyy'
Properties = #(
'DisplayName'
'Company',
'Department',
'memberof',
'PhysicalDeliveryOfficeName'
)
}
# List groups to check for which will be added as columns
$groupsToCheckFor = 'A_ACC_Group1', 'A_ACC_Group2', 'A_ACC_Group3'
Get-ADUser #params | Where-Object { ($_.Enabled -eq $True) } | ForEach-Object {
$object = [pscustomobject][ordered]#{
GivenName = $_.GivenName
Surname = $_.Surname
DisplayName = $_.DisplayName
Company = $_.Company
Department = $_.Department
SamAccountName = $_.SamAccountName
Location = $_.PhysicalDeliveryOfficeName
}
$groups = ($_.memberof | Get-ADGroup | Where-Object Name -Like 'A_ACC*' | Select-Object -ExpandProperty Name)
$groupsToCheckFor | ForEach-Object {
# add property for each group being checked for
# value is true if group is found in users' groups
$object | Add-Member -NotePropertyName $_ -NotePropertyValue ($_ -in $groups)
}
# output object
$object
} | Export-Csv -Path 'H:\My Documents\PowerShell_exports\Users_export.csv' -NoTypeInformation -Encoding UTF8

Grouping data from Active Directory

wondering if i could get some help with my powershell script. I am halfway (or maybe even 3/4 of the way there) but im just struggling to get my groups grouped on one line per user....
Ill explain
Right now i'm able to get ALL users from Multiple AD groups, took a bit of playing around but i got there in the end...
However it displays CSV like this:
first_name,last_name,email,group_list
John,Smith,JSmith#email.com,Group1
John,Smith,JSmith#email.com,Group2
Emily,Rogers,ERogers#email.com,Group1
Emily,Rogers,ERogers#email.com,Group3
Whilst thats OK, i would really like to format the data like this:
first_name,last_name,email,group_list
John,Smith,JSmith#email.com,Group1~Group2
Emily,Rogers,ERogers#email.com,Group1~Group3
This is my code so far
## Define the groups, This includes a wildcard which gets all users in groups with that pattern
$Groups = (Get-AdGroup -filter * | Where {$_.name -like "GroupName*"} | select name -expandproperty name)
## Var for array, empty
$Array = #()
## Var for data
$Data = [ordered]#{
}
## For Each loop to get members of each group
Foreach ($Group in $Groups)
{
## Define the search criteria for AD Search
$Members = Get-ADGroupMember -identity $Group | Get-ADUser -Properties * | select givenName,sn,sAMAccountName,mail
foreach ($Member in $Members)
{
$Data."first_name" = $Member.givenName
$Data."last_name" = $Member.sn
$Data."email" = $Member.mail
$Data."group_list" = $Group
## Store in PSObject
$DataPSObject = New-Object PSObject -property $Data
## Add to array so it is no longer empty
$Array += $DataPSObject
}
}
## Export array into CSV
$Array | export-csv "C:\temp\DataFromArray.csv" -NoTypeInformation
As the email is the unique identifier, i tried to Group-Object on the email property but the output is not useful for me
## Export array into CSV
$Array | Group-Object -Property email | export-csv "C:\temp\DataFromArray.csv" -NoTypeInformation
Also i tried to join the groups using a defined separator -join '~' but this just seemed to create one long string of joined groups (makes sense when i put it that way)
Hoping anyone has some ideas?
Thanks
You need to do a little more processing on the output of Group-Object, but you're almost there!
$Array |Group-Object -Property email |ForEach-Object {
[pscustomobject]#{
first_name = $_.Group[0].first_name
last_name = $_.Group[0].last_name
email = $_.Group[0].email
groups = $_.Group.group_list -join '~' # join all the group names together
}
} |Export-Csv "C:\temp\DataFromArray.csv" -NoTypeInformation
Just a quick thingy to get what you want:
## Define the groups, This includes a wildcard which gets all users in groups with that pattern
$Groups = Get-AdGroup -Filter 'Name -like "GroupName*"' | Select-Object -ExpandProperty Name
## For Each loop to get members of each group
$Array = foreach ($Group in $Groups) {
## Define the search criteria for AD Search and capture in variable $Array
Get-ADGroupMember -Identity $Group |
Get-ADUser -Properties GivenName,Surname,SamAccountName,EmailAddress |
Select-Object #{Name = 'first_name'; Expression = {$_.GivenName}},
#{Name = 'last_name'; Expression = {$_.Surname}},
#{Name = 'email'; Expression = {$_.EmailAddress}},
#{Name = 'GroupName'; Expression = {$Group}}
}
$out = $Array | Group-Object email | ForEach-Object {
# join the GroupName property of this user to get a delimited string
$grouplist = $_.Group.GroupName -join '; '
# output a new object with the 'group_list' property
# this will create duplicates objects, so we need Select-Object * -Unique at the end
$_.Group | Select-Object first_name, last_name, email, #{Name = 'group_list'; Expression = {$grouplist}}
} | Select-Object * -Unique
$out | Export-Csv "C:\temp\DataFromArray.csv" -NoTypeInformation
Hope that helps

Powershell AD: counting groups in office

I am having issues in my thought process. I want an output of something like
'Group Name' 'Title' 'Count' and I am able to get the 'Group Name' and 'Title' Columns but I'm not sure how to count, say, all of the repeat groups in the 'sales' title. Where my code is right now, is searching the entire column to see if it finds that group name, and if it finds that column and then it move the counter by 1 (this is wrong because it my find a group name with a different title)
Any help would be greatly appreciated! Thank you
$allOffices = get-aduser -filter * -Properties physicaldeliveryofficename | Select-Object -ExpandProperty physicaldeliveryofficename #Selecting all offices
$uniOffices = $allOffices | Select-Object -Unique
$groups = $null
$global:ErrorActionPreference = 'silentlycontinue'
$finallist = #()
$count = 0
foreach ($office in $uniOffices) { # Loop through all unique offices
$peopleInOffice = $null
$peopleInOffice = get-aduser -filter * -properties office | Where-Object office -eq $office | Select-Object -ExpandProperty SamAccountName # Array of people in a certain office, selects username
foreach ($person in $peopleInOffice){
$groups = get-ADPrincipalGroupMembership $person
foreach($group in $groups) {
if(($finallist.'Group Name'-contains $group.name) -and ($finallist.'Title' -contains $office)){$count++}
$finallist+= $group| Select-Object #{N='Group Name';E={$group.name}},#{N='Title';E={($office)}},#{N='Count';E={$count}}
}
}
}
$finallist
So I started working on your problem, and I'm unsure what you're asking. Here's a greatly simplified and more efficient representation of your code:
$offices = Get-ADUser -Filter 'PhysicalDeliveryOfficeName -like "*"' -Properties PhysicalDeliveryOfficeName |
Select-Object -ExpandProperty PhysicalDeliveryOfficeName -Unique
$list = foreach ($office in $offices)
{
foreach ($person in (Get-ADUser -Filter "Office -eq '$office'" -Properties Office, MemberOf))
{
$person.MemberOf |
Get-ADGroup -Properties Name |
Select-Object -Property #(
#{
N = 'Group Name'
E = {$PSItem.Name}
}
#{
N = 'Title'
E = {$office}
}
)
}
}
At the end, you have a $list object which is an array of pscustomobject objects that you can manipulate. You can use Group-Object to determine how many groups there are with similar names using calculated properties, etc.
Using your problem statement:
how to count all of the repeat groups in the sales title
$list | Group-Object -Property Title | Select-Object -ExpandProperty Count

Get-ADUser across domains - Help needed

I have the following code:
$FilePath_Prefix = "C:\temp\UserLastLogon-"
function Msg ($Txt="") {
Write-Host "$([DateTime]::Now) $Txt"
}
#Cycle each DC and gather user account lastlogon attributes
$List = #() #Define Array
(Get-ADDomain).ReplicaDirectoryServers | Sort | % {
$DC = $_
Msg "Reading $DC"
$List += Get-ADUser -Server $_ -Filter "samaccountname -like '*'" -Properties LastLogon |
Select samaccountname, lastlogon, #{n='DC';e={$DC}}
}
Msg "Sorting for most recent lastlogon"
$LatestLogOn = #() #Define Array
$List | Group-Object -Property samaccountname | % {
$LatestLogOn += ($_.Group | Sort -prop lastlogon -Descending)[0]
}
$List.Clear()
$FileName = "$FilePath_Prefix$([DateTime]::Now.ToString("yyyyMMdd-HHmmss")).csv"
try {
$LatestLogOn |
Select samaccountname, lastlogon,
#{n='lastlogondatetime';e={[datetime]::FromFileTime($_.lastlogon)}}, DC |
Export-CSV -Path $FileName -NoTypeInformation -Force
Msg "Exported results. $FileName"
} catch {
Msg "Export Failed. $FileName"
}
I use it to interrogate AD Users for most up-to-date lastLogon information across all my domains. It works, and it works really fast.
Now, I need to get more details into my output, such as givenName and Surname lets say.
What would be the best approach to achieve this, because I don't want to interrogate redundantly all my DC's for those kind of attributes.
My idea here, is to create another array with Get-ADuser -Filter * -Properties givenName, surname, etc..etc and then bind together the two arrays. And I don't seem to get it right. Could someone help, or point me in the right direction to achieve this task.
I would fetch all the last logon information and save it into a hash table, which is designed for fast lookups. I'd try something like this:
$FileName = 'C:\temp\UserLastLogon-{0:yyyyMMdd-HHmmss}.csv' -f [DateTime]::Now
$DCs = (Get-ADDomain).ReplicaDirectoryServers | Sort-Object
# This *may* be more semantically accurate; I don't remember how it works with multiple domains
# $DC = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName | Sort-Object
# Create the hash table
$UserLogonInfo = #{}
foreach ($DC in $DCs) {
Write-Host "Reading logon data from $DC..."
# Fetch all users that have a LastLogon value from the current DC
# We specify LastLogon>=1 because some users that never log on have LastLogon of
# 0 or null, both of which would show up as Jan 1, 1601.
Get-ADUser -Server $DC -LDAPFilter '(LastLogon>=1)' -Properties LastLogon | ForEach-Object {
if (!$UserLogonInfo.ContainsKey($_.DistinguishedName)) {
# If the accountname doesn't exist, add it
$UserLogonInfo[$_.DistinguishedName] = #{
LastLogon = $_.LastLogon
LastLogonDateTime = ([DateTime]::FromFileTime($_.LastLogon))
LastLogonDC = $DC
}
}
elseif (($UserLogonInfo[$_.DistinguishedName].LastLogon -lt $_.LastLogon)) {
# If the account name exists, update it if it's more recent
$UserLogonInfo[$_.DistinguishedName] = #{
LastLogon = $_.LastLogon
LastLogonDateTime = ([DateTime]::FromFileTime($_.LastLogon))
LastLogonDC = $DC
}
}
}
}
Write-Host "Fetching user data..."
Get-ADUser -Filter * -Properties LastLogon, givenName, surname |
Select-Object -Property SamAccountName, givenName, surname,
#{n='LastLogonDateTime';e={$UserLogonInfo[$_.DistinguishedName].LastLogonDateTime}},
#{n='LastLogonDC';e={$UserLogonInfo[$_.DistinguishedName].LastLogonDC}} |
Export-CSV -Path $FileName -NoTypeInformation -Force
If an account has a blank LastLogonDateTime and blank LastLogonDC, then that account has never logged on.
It's more correct to use DistinguishedName instead of SamAccountName as the key for the $UserLogonInfo hash table, and that is essentially a required change if you are querying multiple domains at once. Note that I do mean multiple domains and not merely multiple domain controllers in the same domain (which is what I believe you're actually doing in spite of the question title).
This whole process on my domain with 3 DCs and ~10,000 users takes about 15 seconds.
Note that there are a ton of ways that LastLogon can be inaccurate. It can be updated without a full logon or without an interactive logon and some logons won't force an update of the field. If you really want to track logons, you should use security auditing for logon events.
Edit:
When we populate $UserLogonInfo we're fetching all accounts except for accounts that either don't have a LogonDate attribute at all or when that attribute is 0 or null. Each of those indicate that there has been no login. So, we know that any user that isn't in the $UserLogonInfo hash table has never logged in.
If you want to use some special value for when a user account has never logged on, you should just use an if statement and check to see if the user is in the $UserLogonInfo hash table:
Get-ADUser -Filter * -Properties LastLogon, givenName, surname |
Select-Object -Property SamAccountName, givenName, surname,
#{n = 'LastLogonDateTime'; e = {if ($UserLogonInfo.ContainsKey($_.DistinguishedName)) { $UserLogonInfo[$_.DistinguishedName].LastLogonDateTime } else { 'Never' }}},
#{n = 'LastLogonDC'; e = {if ($UserLogonInfo.ContainsKey($_.DistinguishedName)) { $UserLogonInfo[$_.DistinguishedName].LastLogonDC } else { 'N/A' }}} |
Export-CSV -Path $FileName -NoTypeInformation -Force
The thing is that you're actually asking your DCs for that data as it's returned by default. So the only thing to add is
, givenName, surname
to your Select-Object. Check this by running
Get-AdUser yourLogin -Properties LastLogon
You'll receive the following properties in your output: DistinguishedName Enabled GivenName LastLogon
Name
ObjectClass
ObjectGUID
SamAccountName
SID
Surname
UserPrincipalName
So the only thing you lose will be some memory. The alternative approach would be to create another array using
$names = Get-ADuser -Filter * | Select-Object SamAccountName, givenName, surname
and then match the date based on SamAccountName like this:
$LatestLogOn = #() #Define Array
$List | Group-Object -Property samaccountname | % {
$t = ($_.Group | Sort -prop lastlogon -Descending)[0]
$n = $names | Where-Object samaccountname -eq $t.samaccountname
$LatestLogOn += $t | select *, #{n="givenName";e={$n.givenName}}, #{n="surname";e={$n.surname}}
}