I have an object called $data. I want to loop through that object to get ADUsers then do some work with that user. The problem I'm having is that the filter is not returning anything. Here is what I have.
foreach($object in $data)
{
$ADuser = Get-ADUser -filter * -Properties * -SearchBase "$($object.ouPath)" |
? { $_.objectGUID -eq $object.GUID -and $_.employeeNumber -eq $object.personID } |
Select-Object employeeNumber,
SamAccountName,
Enabled
try
{}
catch
{}
}
$data contains the following information:
personID : 9408
firstName : John
lastName : Doe
GUID : dde044a6-b11a-4c23-a4c3-7dfe798a98ce
ouPath : OU=test,DC=my,DC=domain
If your query without the conditions in the Where-Object clause works, then there either is no user with that combination of attributes, OR you are mistaking EmployeeNumber with EmployeeID.
Also, getting all users first with all of their properties and filtering out the one user you seek after that is wasteful. Better use the -Filter parameter which gets things done way faster.
Something like:
foreach($object in $data) {
# check if you don't need the EmployeeID attribute instead of EmployeeNumber
$filter = "ObjectGUID -eq '$($object.GUID)' -and EmployeeNumber -eq '$($object.personID)'"
$ADuser = Get-ADUser -Filter $filter -Properties EmployeeNumber -SearchBase $object.ouPath -ErrorAction SilentlyContinue
if ($ADuser) {
# user found, do what needs to be done here. For demo, just output to console
$ADuser | Select-Object EmployeeNumber, SamAccountName, Enabled
}
else {
Write-Warning "Could not find user with ObjectGUID = '$($object.GUID)' and EmployeeNumber = '$($object.personID)'"
}
}
Related
Recently completed an Azure AD provisioning integration between SuccessFactors and On-Prem AD.
In order for some of our existing users to get 'scoped in' to the Update provisioning, they first need to match on employee id (we currently do not use the Create functionality).
There are about 400 users that we've identified need to be matched, and our HR team has provided us with a csv with the following attributes (Full Name, EmployeeID). I need to somehow compare this file with all users in AD who have no employee id, and if not, update EmployeeId with the contents from the HR provided file.
I'm a bit stuck on how to attack this. Need a Big Brain :)
#import HR file with required attributes "Formal Name, EmployeeId"
#returns ~6500 entries
$SFUsers = Import-Csv Z:\ExportsFromProd\Global_ActiveHeadcountReport_08292022.csv
#returns ~1400 entries
#some accounts never get an employee id
$users = Get-ADUser -Filter "*" -Properties EmployeeID | Where-Object {$_.employeeID -eq $null}
foreach ($account in $users) {
$accountName = $account.name
get-aduser -Filter {Name -eq $accountName} -Properties * | Select-Object samaccountname, displayName
#this is where i need help:
<#
try {
Lookup $SFUser.'Formal Name' in $SFUsers array???
Get $SFUser.'EmployeeID' | set-aduser $account -employeeId $SFUser.'EmployeeId'
}
catch {
}
finally {
}
#>
}
'''
You can use the faster -Filter or LDAPFilter parameters of Get-ADUser to find only users where the EmployeeID property is unset.
Also, your code could be done by using Get-ADUser only once:
#import HR file with required attributes "Formal Name, EmployeeId"
#returns ~6500 entries
$SFUsers = Import-Csv -Path 'Z:\ExportsFromProd\Global_ActiveHeadcountReport_08292022.csv'
#returns ~1400 entries
#some accounts never get an employee id
$users = Get-ADUser -Filter "employeeid -notlike '*'" -Properties DisplayName, EmployeeID
# or use LDAPFilter
# $users = Get-ADUser -LDAPFilter "(!employeeID=*)" -Properties DisplayName, EmployeeID
foreach ($account in $users) {
# try and find this user in the csv file either by .Name or .DisplayName property
$HRUser = $SFUsers | Where-Object { $_.'Formal Name' -eq $account.Name -or
$_.'Formal Name' -eq $account.DisplayName}
if ($HRUser) {
$account | Set-ADUser -EmployeeID $HRUser.EmployeeId
}
else {
Write-Warning "AD user $($account.Name) not found in the CSV file.."
}
}
I've got some logic/formatting brain block here.
I have a CSV with GivenName and Surname Property to use
I need to pipe that info against the AD User Estate and Return the information on the users in the list with a few properties including their name, Office, SamAccountName and Email address. I've got as far as this:
$employees = import-csv 'c:\employees\employeelist.csv'
$UserInfo = ForEach ($user in $employees) { Get-ADUser -Filter * | `
Where-Object { $_.GivenName -like
$employee.GivenName -and $_.Surname -like $employee.Surname
}
The information is returned but not in a table form and i can't believe i cant seem to figure how to pipe it to a CSV, it's not working out, it is returned like this:
Reference : 201111
Surname : Smith
GivenName : Name
Effective from : 24-Sep-13
Business Area : Client Ops
Department : ATE
Organisation Unit : ATE Ops
Any Ideas why when i | export-csv i don't get the correct format?
As commented, you are using the wrong variable name in your foreach loop.
($employee should be $user) since that is the variable you define in the loop.
Something like this:
$employees = Import-Csv 'c:\employees\employeelist.csv'
$UserInfo = foreach ($user in $employees) {
Get-ADUser -Filter * -Properties GivenName, Surname, Office, SamAccountName, EmailAddress |
Where-Object { $user.GivenName -eq $_.GivenName -and $user.Surname -eq $_.Surname } |
Select-Object GivenName, Surname, Office, SamAccountName, EmailAddress
}
$UserInfo | Export-Csv -Path 'c:\employees\employees.csv' -NoTypeInformation
As you can see, I'm also naming the properties you want returned, because Get-ADUser by default returns a subset of properties and withour it, you won't get the Office and EmailAddress properties.
Also, I have changed the -like operator into -eq to fetch exact matches.
P.S. Instead of using the Where-Object construction, the code would be more optimized if you use the -Filter like:
$UserInfo = foreach ($user in $employees) {
Get-ADUser -Filter "GivenName -eq '$($user.GivenName)' -and Surname -eq '$($user.Surname)'" -Properties GivenName, Surname, Office, SamAccountName, EmailAddress |
Select-Object GivenName, Surname, Office, SamAccountName, EmailAddress
}
Any way to block user account in Office365 by EmployeeID instead of UPN?
This is the script I've tried, but it can only block by UPN:
Import-Csv 'C:\BlockedUsers.csv' | ForEach-Object {
$upn = $_."UserPrincipalName"
Set-MsolUser -UserPrincipalName $upn -BlockCredential $true
}
If your csv file contains a column called EmployeeId, you can use that to get the UserPrincipalName property using the Get-AdUser cmdlet:
Import-Csv 'C:\BlockedUsers.csv' | ForEach-Object {
$user = Get-ADUser -Properties EmployeeID -Filter "EmployeeID -eq $($_.EmployeeID)"
if ($user) {
$upn = $user.UserPrincipleName
Set-MsolUser -UserPrincipalName $upn -BlockCredential $true
}
}
Edit
Judging by your comment, it seems the EmployeeID property is not always unique in your organization.
In that case, the below code should be able to handle that
Import-Csv 'C:\BlockedUsers.csv' | ForEach-Object {
$user = Get-ADUser -Properties EmployeeID -Filter "EmployeeID -eq $($_.EmployeeID)"
if ($user) {
foreach ($usr in $user) {
Write-Host "Blocking user $($usr.Name)"
$upn = $usr.UserPrincipleName
Set-MsolUser -UserPrincipalName $upn -BlockCredential $true
}
}
else {
Write-Host "User with EmployeeID $($_.EmployeeID) not found"
}
}
P.S. If your CSV can contain empty values for the EmployeeID column, change the first line into
Import-Csv 'C:\BlockedUsers.csv' | Where-Object {$_.EmployeeID -match '\S'} | ForEach-Object {
to get rid of empty or whitespace-only values.
Edit
If you are sure your CSV contains a column EmployeeId and you are not mistaking that with AD property EmployeeNumber, then perhaps this might work for you.
It uses Get-ADUser to get a collection of user objects that actually have something in their EmployeeId attribute and refines that by comparing to the ones you have read from the CSV using Where-Object.
Both EmployeeId and EmployeeNUmber are AD properties of type String. You can look that up here
# first read the CSV into an array containing only the values from the 'EmployeeID' column
$blockedUserIds = Import-Csv 'C:\BlockedUsers.csv' | Select-Object -ExpandProperty EmployeeId -Unique
# next get an array of user objects that have something in the EmployeeID attribute and only
# leave the users where the attribute can be matched to a value captured in the CSV array above
# use the '#(..)' syntax to force the result to be an array, even if only one item is found
$usersToBlock = #(Get-ADUser -Properties EmployeeID, Name, UserPrincipalName -Filter "EmployeeID -like '*'" |
Where-Object { $blockedUserIds -contains $_.EmployeeID })
# you can also use the '-LDAPFilter' parameter
# $usersToBlock = #(Get-ADUser -Properties EmployeeID, Name, UserPrincipalName -LDAPFilter "(employeeID=*)" |
# Where-Object { $blockedUserIds -contains $_.EmployeeID })
# you now should have an array of user objects that need to be blocked
if ($usersToBlock.Count) {
Write-Host "Blocking $($usersToBlock.Count) users.." -ForegroundColor Green
$usersToBlock | ForEach-Object {
Write-Host "Blocking user $($_.Name)"
Set-MsolUser -UserPrincipalName $($_.UserPrincipleName) -BlockCredential $true
}
}
else {
Write-Warning "No users found with an EmployeeId property that matches any of the values in BlockedUsers.csv"
}
I'm looking for some guidance on creating a powershell script that will check security and distribution groups from specific OU's and see if the owner is a user who's disabled.
We have lots of old groups in our AD created by ex employees that need to be cleaned up.
This is what i've started with.
$managedByGroups = get-adgroup -filter 'groupCategory -eq "Distribution"' -SearchBase "OU=SydExchangeGroups,OU=SydGroups,OU=Sydney,DC=my,DC=org,DC=biz" -Properties distinguishedname, managedby | select sAMAccountName, managedby
$disabledUsers = Get-ADUser -Filter {Enabled -eq $false} -SearchBase "OU=SydDisabledUsers,OU=SydMisc,OU=Sydney,DC=my,DC=org,DC=biz" | select distinguishedname
foreach ($group in $managedByGroups){
if($managedByGroups.managedby -eq $disabledUsers.distinguishedname)
{
write-output
}
}
Thanks
There are a number of issues with your if block:
you are looping through $managedByGroups, but you are never using that variable (it should be $group.managedby)
you are trying to compare 1 element with a list of elements, in this case consider using -in operator instead of -eq.
you should treat the case when there is no value for managedby attribute, in case you do not get the desired results.
An alternative to your code may is below.
I'm first getting the list of managedby users, then i'm looping though each entry, and if it is not null, we try to do a get-aduser filtering by enabled status and the distinguishedname.
$DisabledManagedBy variable will contains ADUser objects which are disabled.
$grp = get-adgroup -filter 'groupCategory -eq "Distribution"' -Properties ManagedBy,DistinguishedName
$DisabledManagedBy = foreach ($item in $grp.ManagedBy) {
if ($item) {
Get-ADUser -Filter {Enabled -eq $false -and DistinguishedName -like $item} -Properties DistinguishedName
}
}
I worked this out eventually by doing the following:
$myDisabledUsers = #()
$date = get-date -format dd-MM-yyyy
$managedSydGroups = Get-ADGroup -Filter * -Properties * -Searchbase "OU=SydExchangeGroups,OU=SydGroups,OU=Sydney,DC=my,DC=biz,DC=org" | where {$_.managedby -ne $null} | select name, managedby
$disabledSydUser = Get-ADUser -Filter * -SearchBase "OU=SydDisabledUsers,OU=SydMisc,OU=Sydney,DC=my,DC=biz,DC=org" | where {$_.enabled -eq $false} | select -ExpandProperty distinguishedname
$disabledOwners = foreach($group in $managedSydGroups)
{
$managedByString = [string]$group.managedby
if($disabledSydUser -contains $managedByString)
{$myDisabledUsers += $group}
}
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}}
}