I'm using Powershell to update the manager attribute in AD using data from 2 txt files.
One txt file has employee IDs in one column their manager's ID in the other like this:
1111 2222
4444 3333
The other txt file has all employee info listed by IDs like this:
1111 POTTER HARRY HPOTTER 200 3108675309
2222 GRANGER HERMIONE HGRANGER 201 3107942312
HPOTTER and HGRANGER are the samaccountnames. I want to use the IDs to grab all the samaccountnames to update the manager attribute for each employee.
There are several employeeIDs listed in the first txt file that are not listed in the second txt file (they are no longer employed). I thought that wouldn't really matter, but what I wrote so far hasn't been updating every employee correctly. I rewrote it and am stuck on how to loop through $employees to get both the employee samaccountname and the manager samaccountname.
$employees = Get-Content -Path 'Staff.txt'
$supervisors = Get-Content -Path 'Supervisors.txt'
foreach($supervisor in $supervisors){
$employeeID = $supervisor.substring(0,7)
$managerID = $supervisor.substring(8,7)
foreach($employee in $employees){
if ($employee.contains($employeeID)){
$empsamName = $employee.split("`t")[3]
#some code to get the manSamName
$ADUser = Get-ADUser -Filter "samaccountname -like '$empSamName'"
$manager = Get-ADUser -Filter "samaccountname -like '$manSamName'"
if($ADUser -and $manager){
$ADUser | Set-ADUser -manager $manager
}
}
}
}
Thanks for the help!
Instead of running a loop through each line to check if it matches the employee/manager ID, uou would want to use Where-Object to find a match.
Example
$employees | Where-Object {$_ -like "*1111*"}
I've provided a full solution below that's a little more thorough, allowing you to reference each employee as a separate object, and their attributes using dot-notation
Result
Line 1
EmployeeID
EmployeeSamAccountName
ManagerID
Manager
1111
HPOTTER
2222
HGRANGER
Line 2
Employee ID 4444 no longer employed
EmployeeID
EmployeeSamAccountName
ManagerID
Manager
4444
3333
Code
$employees = Get-Content -Path 'Staff.txt'
$supervisors = Get-Content -Path 'Supervisors.txt'
$employeeTable = foreach ($employee in $employees) {
# From the staff file, split each column into an array
$employeeSplit = $employee.Split(" ")
# Create a table from all the split columns, and add this employee object into another array (the $employeeTable)
[PSCustomObject] #{
EmployeeID = $employeeSplit[0]
EmployeeFirstName = $employeeSplit[1]
EmployeeSurname = $employeeSplit[2]
EmployeeSamAccountName = $employeeSplit[3]
Manager = "" # Leave this blank #
Column5 = $employeeSplit[4]
Column6 = $employeeSplit[5]
}
}
foreach ($supervisor in $supervisors) {
# From the supervisor file, split the 2 columns into separate variables
$employeeID, $managerID = $supervisor.Split(" ")
# From the employee table, get the employee that matches the ID from column 1 of the supervisors file
$employee = ($employeeTable | Where-Object { $_.EmployeeId -eq $employeeId } )
# Continue if the employee is still employed
if ($employee -ne $null) {
# Update the employee table to add the manager's name
$employee.Manager = ($employeeTable | Where-Object { $_.EmployeeId -eq $managerID } ).EmployeeSamAccountName
# Do your stuff
$ADUser = Get-ADUser -Filter "samAccountName -eq '$($employeeTable.EmployeeSamAccountName)'"
$manager = Get-ADUser -Filter "samAccountName -eq '$($employeeTable.Manager)'"
}
# Build a new table for the supervisor file
$outputTable = [PSCustomObject] #{
EmployeeID = $employeeID
EmployeeSamAccountName = $($employee.EmployeeSamAccountName)
ManagerID = $managerId
Manager = $($employee.Manager)
}
# Sample output
Write-Host "Line $($supervisors.IndexOf($supervisor))" -BackgroundColor DarkCyan
if ($employee.EmployeeSamAccountName -eq $null) {
Write-Host "Employee ID $($employeeID) no longer employed" -ForegroundColor Red
}
Write-Host ($outputTable | ft | Out-String)
}
Related
I wrote a PowerShell script that searches for a user based on given input and then removes said user from all groups (except for Domain Users). However, while you can't have a user with the same name in an OU group, you can have a user with the same name in a different OU group in the organization. Would it be possible to search for a user (John Smith), and allow one to select which user to remove from all groups if a duplicate user is returned? Here is my script so far. It works, but this is the functionality I would like to add.
#Requires -Module ActiveDirectory
Import-Module ActiveDirectory
function Disable-ADUser{
$msg = "Do you want to remove a user from all Security groups? [Y/N]"
do {
$response = Read-Host -Prompt $msg
if ($response -eq "y") { # Beginning of if statment
#Asks user via a text prompt to ender the firstname and lastname of the end user to remove
$firstName = Read-Host "Please provide the First name of the User"
$lastName = Read-Host "Please provide the Last name of the User"
#The user's samaccoutname is found by searching exactly for the user's first name and lastname given in the above prompts
$samName = Get-ADUser -Filter "GivenName -eq '$firstName' -and Surname -eq '$lastName'"| Select-Object -ExpandProperty "SamAccountName"
#All of the user's groups are queried based on their sam name
$listGroups = Get-ADUser -Identity $samName -Properties MemberOf | Select-Object -ExpandProperty MemberOf
#All of the user's groups are placed in an array
[System.Collections.ArrayList]$groupsArray = #($listGroups)
#Every group in the groupsArray is cycled through
foreach ($group in $groupsArray) {
#A text output is displayed before the user is removed from each group listed in the above array
#Once all groups have been cycled through, the for loop stops looping
Write-Host "Removing $samName " -f green -NoNewline
Write-Host "from $group" -f red
$OutputLine="Removing $samName from $group"
Out-File -FilePath remove_user_groups.log -InputObject $OutputLine -Append
Remove-ADGroupMember -Identity $group -Members $samName
}
} # End of if statement
} until ($response -eq "n")
}
Disable-ADUser
I use Out-GridView. It allows me to select user(s) with mouse, or to select no one. See -OutputMode parameter.
<# Example part #>
$data = #'
[
{displayName: "Don Pedro Fizikello", employeeNumber: "Emp001", phone: "+888888888" },
{displayName: "Don Pedro Gonzalez", employeeNumber: "Emp002", phone: "+77777777777" },
{displayName: "Natalia Marisa Oreiro", employeeNumber: "Emp456", phone: "+987654321" },
{displayName: "Juan Carlos Rodrigez", employeeNumber: "Emp123", phone: "+1234567890"}
]
'# | ConvertFrom-Json
$userList = #($data | Where-Object { $_.displayName -like 'Don*' })
#Real-world case from Active Directory: $userList = #( Get-ADUser -Filter "(displayName -like 'Don*')" -Properties #('displayName', 'phone') )
<# /Example part #>
$user = $null
if ($userList.Count -eq 1) {
$user = $userList[0] # // The only entry
} elseif ($userList.Count -gt 1) {
$user = $userList | Out-GridView -OutputMode Single -Title 'Select User you want co tall to or press cancel'
}
if ($null -eq $user) {
# // There is no users found or selected by human
Write-Host "Nothing to do" -f Yellow
} else {
# // Work with User
Write-Host "Call $($user.displayName) : $($user.phone)" -f Green
}
The negative option is that Out-GridView can not hide parameters that it will display. There are some workarounds depending on task. Example: I show only DisplayName and some ID in Out-GridView (no phone property), but I use returned ID to take full user (with phone) from cache I've created before.
This allows me not to break the original object ( if it's from Get-ADUser, it contains tons of human-useless data like SID, GUID, ObjectClass, ObjectCategory, etc. )
<# Example part #>
Same as previous
<# /Example part #>
$user = $null
$userCacheOriginal = #{}
$userCacheCut = #{}
for ($i = 0; $i -lt $userList.Count; $i++)
{
# !! Here I assign some entryUniqueId to two collections -
# - userCacheOriginal - Original user object with Phone field ( and others )
# - userCacheCut - Transformed objects that contains only ID and info I want to show in Out-GridView
$entryUniqueId = "Idx$($i)"
$userCacheOriginal[$entryUniqueId] = $userList[$i]
$userCacheCut[$entryUniqueId] = [PSCustomObject]#{ID = $entryUniqueId; displayName = $userList[$i].DisplayName;}
}
if ($userList.Count -eq 1) {
$user = $userList[0] # // The only entry
} elseif ($userList.Count -gt 1) {
$userChoice = $userCacheCut.Values | <# Set order of columns this way#> Select #('ID', 'DisplayName') | Out-GridView -OutputMode Single -Title 'Select User or press cancel'
if (($null -ne $userChoice.ID) -and ($userCacheOriginal.ContainsKey($userChoice.ID))) # Check if returned value contains ID,
{ # And select original user object from userCacheOriginal
$user = $userCacheOriginal[$userChoice.ID]
}
}
if ($null -eq $user) {
# // There is no users found or selected by human
Write-Host "Nothing to do" -f Yellow
} else {
# // Work with User
Write-Host "Call $($user.displayName) : $($user.phone)" -f Green
}
If you want to keep it console based, you can add a while loop that requires further input from the user.
#The user's samaccoutname is found by searching exactly for the user's first name and lastname given in the above prompts
$samName = Get-ADUser -Filter "GivenName -eq '$firstName' -and Surname -eq '$lastName'"|
Select-Object -ExpandProperty "SamAccountName"
if ($samname.count -gt 1) {
$newsamname = $null
while ($newsamname -notin $samname) {
$newsamname = Read-Host "Multiple names were found:`n$($samname -join ""`n"")`nPlease type the SamAccountName of the target user"
}
$samname = $newsamname
}
The idea is if multiple user objects are found, then $samname will initially be a collection of count greater than one. Here the executor will be required to enter a valid SamAccountName value from the presented list. Otherwise, the loop will go on forever until the program is manually halted. You could build in a counter to automatically exit the program after a certain number of retries or exit when no value is entered. You could implement a menu system where a number can be entered, which corresponds to the index of the list.
I using the following POWER SHELL script, to extract ( to csv ) managers name , from a "Manager" user attribute.
#This script, , Exports the Manager name of the employee`s in the TXT file.
# users.txt file - contains a simply list of user names ( samaccount-names )
Get-Content D:\powershell\permmisions\Users.txt | Foreach-Object {
Get-ADUser -Identity $_ -Properties Manager | Select-Object name, Manager | Export-Csv D:\Powershell\ADuserinformation\Export-Managers-of-specific-users.csv
-Append
}
The challenge i am facing, is when is on the exported CSV file,
the list "SKIPS" blank value-fields,In case there is no manager set for the user.
And a ROWS is not created , where MANAGER is missing.
What i would like to do , is the script to enter a charcter ( ~ ) for example, where, value is blank.
That way , a row will be created for the blank MANAGER value, on the CSV file
Please help ,
Thanks all in advance.
Note: At least the Name property should exist on all AD users retrieved, so you would get a row even for users where Manager is empty, but with an empty Manager column. If you do need to deal with possibly not all users named in Users.txt actually existing, see Theo's helpful answer.
The simplest approach is to use a calculated property:
Get-ADUser -Identity $_ -Properties Manager |
Select-Object Name, #{ Name='Manager';
Expression={ if ($_.Manager) { $_.Manager } else { '~' } } }
Note:
It is common to abbreviate the key names of the hashtable that defines the calculated property to n and e.
The if statement takes advantage of the fact that an empty string (or $null) evaluates to $false in a Boolean context; for an overview of PowerShell's implicit to-Boolean conversion, see the bottom section of this answer.
In PowerShell [Core] 7.0 or above, you could additionally take advantage of the ternary operator (<condition> ? <valueIfTrue> : <valueIfFalse>) to further shorten the command:
# PSv7+
Get-ADUser -Identity $_ -Properties Manager |
Select-Object Name, #{ n='Manager'; e={ $_.Manager ? $_.Manager : '~' } }
Note: If $_.Manager were to return $null rather than the empty string ('') if no manager is assigned, you could use ??, the PSv7+ null-coalescing operator instead: $_.Manager ?? '~'
Not concise at all, but this allows you to insert more properties of interest in your report, and does some error-checking if the user listed in your input file does not exist:
$report = foreach ($account in (Get-Content D:\powershell\permmisions\Users.txt)) {
$user = Get-ADUser -Filter "SamAccountName -eq '$account'" -Properties Manager -ErrorAction SilentlyContinue
if ($user) {
if (!$user.Manager) { $mgr = '~' }
else {
# the Manager property is the DistinghuishedName for the manager.
# if you want that in your report, just do
$mgr = $user.Manager
# if you want the Name for instance of that manager in your report,
# comment out the above line and do this instead:
# $mgr = (Get-ADUser -Identity $user.Manager).Name
}
# now output an object
[PsCustomObject]#{
UserName = $user.Name
Manager = $mgr
}
}
else {
Write-Warning "User '$account' does not exist"
}
}
# output on screen
$report | Format-Table -AutoSize
# output to CSV file
$report | Export-Csv -Path 'D:\Powershell\ADuserinformation\Export-Managers-of-specific-users.csv' -NoTypeInformation
If I have a .csv:
ClientCode,GroupCode
1234,ABC
1234,DEF
1235,ABC
1236,ABC
and I want to get a hashtable with ClientCode as key, and values to be all AD groups with ClientCode in it, for example:
ClientCode GroupCode
---------- ---------
1234 ClientGroup_InAD_1234, some_other_client_1234
1235 ClientGroup_InAD_1235, some_other_client_in_AD_1235
1236 ClientGroup_InAD_1236
How do I go about this?
Essentially, I have client groups in Active Directory and each client has a code which is the same as the 'ClientCode' in the csv. For example, I might have a client called 'Bob' which I have assigned a code '1234' to it. Therefore, the Group for Bob in the AD would be 'Bob_1234'. Essentially I want to be able to search for whatever groups have ClientCode in them. So i want to search for the all the AD groups that have '1234'. This would return 'Bob_1234' and whatever group in the AD also has '1234' in its name.
So far I have tried:
$clientTable = #{}
foreach($rec in $csv_data) {
$groups = #(get-adgroup -Filter "name -like '*$($rec.clientcode)_*'")
write-host "Found $($groups.count) group(s) for: $($rec.clientcode)"
$clientTable[$ClientCode] = #($groups)
}
$clientTable
but I'm not getting my desired output
You can use the loop like this. You will need to search with a * at the beginning of the name you are looking to find via the Filter.
foreach($rec in $csv) {
$clientCode = "*_$($rec.ClientCode)"
if (!($clientTable.ContainsKey($clientCode))) {
$names = Get-ADGroup -Filter 'Name -like $clientCode' | select Name
$clientTable[$clientCode] = $names -join ","
}
}
This will also check for any client IDs that have already been checked and ignore those.
If you want a hash table populated with the ClientCode value as the key name, you can do the following:
$clientTable = #{}
foreach($rec in $csv_data){
$groups = #(Get-ADGroup -Filter "Name -like '*_$($rec.ClientCode)'" | Select -Expand Name)
write-host "Found $($groups.count) group(s) for: $($rec.ClientCode)"
$clientTable[$rec.ClientCode] = $groups
}
$clientTable
Keep in mind here that each value in the hash table is an array of group names. If you want a single string with comma-delimited names, you can do $clientTable[$rec.ClientCode] = $groups -join "," instead.
You will need to de-duplicate the ClientCodes in the CSV before retrieving the groups.
Something like below should do it (assuming the ClientCode is always preceded by an underscore like in _1234 as shown in your examples)
$csv = Import-Csv -Path 'ClientGroupCodes.csv'
$clientTable = #{}
$csv | Select-Object -ExpandProperty ClientCode -Unique | ForEach-Object {
# $_ is a single clientcode in each iteration (string)
# get an array of goup names
$groups = #(Get-ADGroup -Filter "Name -like '*_$_'" | Select-Object -ExpandProperty Name)
Write-Host "Found $($groups.Count) group(s) for code : '$_'"
$clientTable[$_] = $groups -join ', '
}
$clientTable
I need to merge two properties into one column, name of user is in DisplayName while Name of group is stored in Name how ever both types of objects has DisplayName, Name properties so I need to show them in one column.
Suppose Group is 'Test Group'
CN : …
ObjectGUID : 123-456-XXXX
ObjectClass: group
DisplayName:
Name: 'Test Group'
And 'Test User' Properties are
CN: …
ObjectGUID : 789-456-XXXX
ObjectClass: user
DisplayName: 'Test User'
Name:
I have tried using a for each looping but can't figure out the use of select statement.
Get-ADGroupMember -Identity $GroupGUID |
ForEach-Object{
if($_.ObjectClass -eq 'User'){
# process user object
$_ | Get-ADUser -Properties DisplayName
}elseif ($_.ObjectClass -eq 'User'){
# process group
$_ | Get-ADGroup -Properties Name
}
}
The expected output need to be
MemberName ObjectGUID
---------------- ------------------
Test Group 123-456-XXXX
Test User 789-456-XXXX
I'd use a switch statement to process each item depending on if it is a user or group, and then replace the MemberName property depending on what it is:
$Results = Switch(Get-ADGroupMember -Identity $GroupGUID){
{$_.ObjectClass -eq 'User'} {$_ | Get-ADUser -Prop DisplayName | Select *,#{l='MemberName';e={$_.DisplayName}} -ExcludeProperty MemberName}
{$_.ObjectClass -eq 'Group'} {$_ | Get-ADGroup | Select *,#{l='MemberName';e={$_.Name}} -ExcludeProperty MemberName}
}
$Results|Select MemberName,ObjectGUID
Or if you really want it all done in the pipeline you could do this:
Get-ADGroupMember -Identity $GroupGUID -PipelineVariable 'Member' | ForEach{If($Member.ObjectClass -eq 'User'){$_ | Get-ADUser -Properties DisplayName}Else{$_ | Get-ADGroup}} | Select #{l='MemberName';e={If($Member.ObjectClass -eq 'User'){$_.DisplayName}Else{$_.Name}}},ObjectGUID
i think you are wanting to merge the sources into one output object.
the usual way to add values from multiple sources into one object is to use a [PSCustomObject] to build a ... custom object. [grin] i don't have any AD access, so this is done with local user & group info. you can do some fairly complex structuring by calculating what you want to work with and stuffing it all into one neatly structured object.
here's a demo of the idea using local accounts ...
$GroupName = 'Homonymic_Tuu'
$MemberList = Get-LocalGroupMember -Group $GroupName |
Where-Object {$_.ObjectClass -eq 'User'}
$GroupInfo = Get-LocalGroup -Name $GroupName
foreach ($ML_Item in $MemberList)
{
$UserInfo = Get-LocalUser -Name $ML_Item.Name.Split('\')[-1]
if ([string]::IsNullOrEmpty($UserInfo.LastLogon))
{
$LastLogon = '_Never_'
}
else
{
$LastLogon = $UserInfo.LastLogon
}
[PSCustomObject]#{
Group_Name = $GroupInfo.Name
Group_Description = $GroupInfo.Description
User_Name = $UserInfo.Name
User_Description = $UserInfo.Description
User_Enabled = $UserInfo.Enabled
User_LastLogon = $LastLogon
}
}
truncated output ...
Group_Name : Homonymic_Tuu
Group_Description : Homonym - sounds like Tuu
User_Name : 22
User_Description : The Digit 2 Twice
User_Enabled : True
User_LastLogon : 2018-11-27 9:19:10 PM
[*...snip...*]
Group_Name : Homonymic_Tuu
Group_Description : Homonym - sounds like Tuu
User_Name : TwoTwo
User_Description : Repeating the name of the number after One.
User_Enabled : True
User_LastLogon : _Never_
the data will also export to a CSV file quite easily. [grin]
I have AD with a lot of computers in it. computer names are made like this: XXXXNNNN (where X=constant part and N=numeric dynamic part of the name). Any thoughts how can I scan names from N=0 --> N=Nmax, and export all unused (free) names to *.txt?
[int]$NMax = (Get-ADComputer -Filter {name -like "XXXX*"} | Sort-Object name | select -ExpandProperty name -Last 1).SubString(4, 4)
$i = 0001
while ($i -ne ($NMax+1))
{
try
{
Get-ADComputer ("XXXX"+$($i.ToString("D4"))) | select name | out-null
}
catch
{
write "No ADComputer with the name: XXXX$($i.ToString("D4"))"
}
$i++
}