Issue bulk creation of users in AD - powershell

I'm trying to create kind of a solution to create thousands of accounts in AD add them to specific group or for service accounts add them to specific OU. Keep a log of what was done and what the errors are.
The script ingest a csv file with the following headers.
SamAccountName,name,password,ou,domain,isAdded
$Domain = [system.directoryservices.activedirectory.domain]::GetCurrentDomain().Name
$NewUserADGroup = 'Print Operators'
$NewUsersList = Import-Csv .\bulk_user1.csv | Where-Object{$_.domain -like "$Domain"}
$NewUsersList | ForEach-Object{
$NewUserAttributes = #{
SamAccountName = $_.SamAccountName
name = $_.name
#path = $_.parentou
#UserPrincipalName = $_."samAccountName" + "#lovely.Local"
AccountPassword = (convertto-securestring "$NewUsersList.password" -AsPlainText -Force)
Enabled = $true
#Server = $dcname
#isAdded = $Issue
}
try{
#Create new User and add to specific group
New-ADUser $NewUserAttributes
Add-ADGroupMember -Identity $NewUserADGroup -Members $_.SamAccountName
#Delete Specific User
#Remove-ADUser -Identity $_.SamAccountName
}catch{
Write-Warning $_
$Issue = $_.ToString()
}
$count = $count + 1
Write-Host $_.SamAccountName " " $_.Name " " $_.SamAccountName.Enabled " Total:" $NewUsersList.Count + "Processed:" $count
$NewUserAttributes| Select-Object -Property SamAccountName,name,AccountPassword,Enabled,isAdded | Export-Csv ".\$Domain.NewAccountsCreatedStatus.csv"
}
I'm getting the following error:
WARNING: The name provided is not a properly formed account name
When I look at the variable
$NewUserAttributes
I do see the name and the value:
Name Value
---- -----
Enabled True
name bfmbsngfilexfer2
AccountPassword System.Security.SecureString
SamAccountName bfmbsngfilexfer2

As promised, below a rewrite of your code.
I have inserted comments to hopefully explain what the code does:
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
$NewUserADGroup = 'Print Operators'
$successCount = 0
$NewUsersList = Import-Csv .\bulk_user1.csv | Where-Object { $_.domain -eq $Domain } | ForEach-Object {
# capture the human readable password for output use
$password = $_.password
$userParams = #{
SamAccountName = $_.SamAccountName
Name = $_.name
Path = $_.parentou
UserPrincipalName = '{0}#lovely.Local' -f $_.SamAccountName
AccountPassword = ConvertTo-SecureString $_.password -AsPlainText -Force
Enabled = $true
#Server = $dcname
}
try{
# Create new User and add to specific group
$user = New-ADUser #userParams -PassThru -ErrorAction Stop
Add-ADGroupMember -Identity $NewUserADGroup -Members $user -ErrorAction Stop
# add the 'isAdded' element to the $userParams hashtable
$userParams['isAdded'] = $true
$successCount++
}
catch{
Write-Warning $_.Exception.Message
$userParams['isAdded'] = $false
}
# output a PsCustomObject with values taken from the Hashtable
# AccountPassword is a SecureString, which will be of no use to you..
# Output the human readable password instead so you can inform the new users.
[PsCustomObject]$userParams | Select-Object SamAccountName, Name,
#{Name = 'Password'; Expression = {$password}},
Enabled, isAdded
}
# output
# use '#($NewUsersList)' to force it as array, so the Count property is accurate
if (#($NewUsersList).Count) {
Write-Host ('Processed: {0} Succeeded: {1}' -f $NewUsersList.Count, $successCount) -ForegroundColor Green
$NewUsersList | Export-Csv ".\$Domain.NewAccountsCreatedStatus.csv" -NoTypeInformation
}
else {
Write-Host 'No users successfully processed!' -ForegroundColor Red
}

Related

Trying to return AD Group members with extended info, but output CSV is showing unexpected, repeated data

My goal is to dump a CSV of our AD groups, their members, and whether those member objects are enabled, but I'm running into a strange (probably self-inflicted) issue, wherein a Foreach-Object loop is behaving unexpectedly.
The output almost works. It dumps a CSV file. The file has rows for each group, populated with the correct group-related data, and the right number of rows, following the number of group members. However, group member properties on those rows is repeated, showing the same user data over and over for each groupmember result, apparently following the properties of the last returned object from Get-ADGroupMember.
To try to diagnose the issue, I added the line Write-Host $GroupMember.Name -ForegroundColor Gray. This is how I knew the entries in the CSV were the last-returned results for each group. Confusingly, the console correctly echoes each group member's display name.
I'm assuming there's some kind of logic error at work here, but I have had no luck finding it. Any help would be appreciated!
clear
Import-Module ActiveDirectory
# CONFIG ========================================
# Plant Number OU to scan. Used in $CSV and in Get-ADComputer's search base.
$PlantNumber = "1234"
# FQDN of DC you want to query against. Used by the Get-AD* commands.
$ServerName = "server.com"
# Output directory for the CSV. Default is [Environment]::GetFolderPath("Desktop"). Used in $CSV. NOTE: If setting up as an automated task, change this to a more sensible place!
$OutputDir = [Environment]::GetFolderPath("Desktop")
# CSV Output string. Default is "$OutputDir\$PlantNumber"+"-ComputersByOS_"+"$(get-date -f yyyy-MM-dd).csv" (+'s used due to underscores in name)
$CSV = "$OutputDir\$PlantNumber"+"GroupMembers_"+"$(get-date -f yyyy-MM-dd).csv"
# Create empty array for storing collated results
$collectionTable = #()
# Get AD groups, return limited properties
Get-AdGroup -filter * -Property Name, SamAccountName, Description, GroupScope -SearchBase "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM" -server $ServerName | Select SamAccountName, Description, GroupScope | Foreach-Object {
Write-Host "Querying" $_.SamAccountName "..."
#Initialize $collectionRow, providing the columns we want to collate
$collectionRow = "" | Select GroupName, GroupScope, GroupDesc, MemberObjectClass, MemberName, MemberDisplayName, Enabled
# Populate Group-level collectionRow properties
$collectionRow.GroupName = $_.SamAccountName
$collectionRow.GroupDesc = $_.Description
$collectionRow.GroupScope = $_.GroupScope
# Process group members
Get-ADGroupMember -Identity ($collectionRow.GroupName) -Server $ServerName -Recursive | ForEach-Object {
$GroupMember = $_
# Echo member name to console
Write-Host $GroupMember.Name -ForegroundColor Gray
$collectionRow.MemberName = $GroupMember.SamAccountName
$collectionRow.MemberDisplayName = $GroupMember.name
$collectionRow.MemberObjectClass = $GroupMember.ObjectClass
# If the member object is a user, collect some additional data
If ($collectionRow.MemberObjectClass -eq "user") {
Try {
$collectionRow.Enabled = (Get-ADUser $GroupMember.SamAccountName -Property Enabled -ErrorAction Stop).Enabled
If ($collectionRow.Enabled -eq "TRUE") {$collectionTable += $collectionRow}
}
Catch {
$collectionRow.Enabled = "ERROR"
$collectionTable += $collectionRow
}
}
}
}
Write-Host "`n"
# Attempt to save results to CSV. If an error occurs, alert the user and try again.
$ExportSuccess = 'false'
while ($ExportSuccess -eq 'false') {
Try
{
# Export results to $CSV
$collectionTable| Export-csv $CSV -NoTypeInformation -ErrorAction Stop
# If the above command is successful, the rest of the Try section will execute. If not, Catch is triggered instead.
$ExportSuccess = 'true'
Write-Host "`nProcessing complete. Results output to"$CSV
}
Catch
{
Write-Host "Error writing to"$CSV"!" -ForegroundColor Yellow
Read-Host -Prompt "Ensure the file is not open, then press any key to try again"
}
}
There are many things from your code you need to fix, I'll just point out the most important ones:
Don't use #() and +=
You keep using 'True' and 'False' which are strings, PowerShell booleans are $true and $false.
There is also too much redundant code. Also ForEach-Object is slow, if your groups have many members and since you're using -Recursive it's better to use a fast loop instead.
$PlantNumber = "1234"
$ServerName = "server.com"
$OutputDir = [Environment]::GetFolderPath("Desktop")
$fileName = "${PlantNumber}GroupMembers_$(Get-Date -f yyyy-MM-dd).csv"
$CSV = Join-Path $OutputDir -ChildPath $fileName
# $collectionTable = #() => Don't do this to collect results, ever
$adGroupParams = #{
# Name and SAM are default, no need to add them
Properties = 'Description', 'GroupScope'
SearchBase = "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM"
Server = $ServerName
Filter = '*'
}
# Get AD groups, return limited properties
$collectionTable = foreach($group in Get-AdGroup #adGroupParams)
{
Write-Host "Querying $($group.samAccountName)..."
foreach($member in Get-ADGroupMember $group -Server $ServerName -Recursive)
{
# if this member is 'user' the Enabled property
# will be a bool ($true / $false) else it will be $null
$enabled = if($member.ObjectClass -eq 'User')
{
(Get-ADUser $member).Enabled
}
[pscustomobject]#{
GroupName = $group.SamAccountName
GroupDesc = $group.Description
GroupScope = $group.GroupScope
MemberName = $member.SamAccountName
MemberDisplayName = $member.Name
MemberObjectClass = $member.ObjectClass
Enabled = $enabled
}
}
}
as i understand, you need to export list of groups with members to a csv file and know if member accounts are enabled or not, if this what you want, you can check the below code
$output = #()
Import-Module ActiveDirectory
$ServerName = "server.com"
$PlantNumber = "1234"
$OutputDir = [Environment]::GetFolderPath("Desktop")
$CSV = "$OutputDir\$PlantNumber"+"GroupMembers_"+"$(get-date -f yyyy-MM-dd).csv"
$groups = Get-AdGroup -filter * -Property Description -SearchBase "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM" -server $ServerName
foreach ($group in $groups){
$members = Get-ADGroupMember -Identity $group.SamAccountName -Recursive
foreach ($member in $members){
$output += [pscustomobject]#{
GroupName = $group.SamAccountName
GroupDesc = $group.Description
GroupScope = $group.GroupScope
MemberName = $member.samaccountname
MemberDisplayName = $member.Name
MemberObjectClass = $member.ObjectClass
Enabled = $(Get-ADUser -Identity $member.samaccountname).enabled
}
}
}
$output | Export-Csv $CSV -NoTypeInformation
I am explicitly NOT refering your code. I'd just like to show how I would approach this task. I hope it'll help you anyway.
$Server = 'Server01.contoso.com'
$SearchBase = 'OU=BaseOU,DC=contoso,DC=com'
$CSVOutputPath = '... CSV path '
$ADGroupList = Get-ADGroup -Filter * -Properties Description -SearchBase $SearchBase -Server $Server
$ADUserList = Get-ADUser -Filter * -Properties Description -SearchBase $SearchBase -Server $Server
$Result =
foreach ($ADGroup in $ADGroupList) {
$ADGroupMemberList = Get-ADGroupMember -Identity $ADGroup.sAMAccountName -Recursive
foreach ($ADGroupmember in $ADGroupMemberList) {
$ADUser = $ADUserList | Where-Object -Property sAMAccountName -EQ -Value $ADGroupmember.sAMAccountName
[PSCustomObject]#{
ADGroupName = $ADGroup.Name
ADGroupDescription = $ADGroup.Description
ADGroupMemberName = $ADUser.Name
ADGroupMemberSamAccountName = $ADUser.sAMAccountName
ADGroupMemberDescription = $ADUser.Description
ADGroupMemberStatus = if ($ADUser.Enabled) { 'enabled' }else { 'diabled' }
}
}
}
$Result |
Export-Csv -Path $CSVOutputPath -NoTypeInformation -Delimiter ',' -Encoding utf8
It'll output only the a few properties but I hope you get the idea.
BTW: The properties DistinguishedName, Enabled, GivenName, Name, ObjectClass, ObjectGUID, SamAccountName, SID, Surname, UserPrincipalName are included in the default return set of Get-ADUser and the properties DistinguishedName, GroupCategory, GroupScope, Name, ObjectClass, ObjectGUID, SamAccountName, SID are included in the default return set of Get-ADGroup. You don't need to query them explicitly with the parameter -Properties.

Email Notification on Upon User Attributes update

im currently working on a automated User Attributes updating scrips, and currently that working fine, i am having issues however collecting the list of changes so they can be bundled inot an email notification for the administrator
here is what i have so far,
i want to form a list of all changes so that i can add that to an send-mailmessage to an admin for each user, but only when there is a change.
at the moment i only get whatever the latest thing changed is, not a list.
$csvFile = 'C:path.csv' # Enter a path to your import CSV file
$validUsernameFormat = '[^a-zA-Z_.]' # identifies anything that's _not_ a-z or underscore or .
$Mailpassword = ConvertTo-SecureString -string “4a1fd5e9f7e26f” -AsPlainText -Force
$MailCred = New-Object System.Management.Automation.PSCredential -argumentlist "38da1ca9daf082", $Mailpassword
$mailBody = $NewUserParams | out-string
# read the input csv and loop through
Import-Csv -Path $csvFile | ForEach-Object {
$firstName = $_.FirstName.Trim()
$surname = $_.Surname.Trim()
#$validUsernameFormat = "[^a-zA-Z_.]" # identifies anything that's _not_ a-z or underscore or .
$vaildusername = "($firstName'.'$surname)" -replace $validUsernameFormat, '' #removes anything that isn'tin $validUsernameFormat
$truncateifgreaterthanXchar = '(?<=^.{20}).*'
$username = $vaildusername -replace $truncateifgreaterthanXchar
$DefaultPassword = 'Pa$$w0rd'
$securePassword = ConvertTo-SecureString -String $DefaultPassword -AsPlainText -Force
# test if a user with that name already exists
$user = Get-ADUser -Filter "SamAccountName -eq '$username'" -ErrorAction SilentlyContinue
if ($user) {
$CurrentAttributes = Get-ADUser -Identity $username -Properties *
# You don't need this line because you are already declaring the variable in the next one
# [psobject]$CorrectAttributes
$CorrectAttributes = #{
SamAccountName = $username
Name = "$firstname $surname"
DisplayName = "$firstname $surname"
UserPrincipalName = "$username#domain.com"
GivenName = $firstname
Surname = $surname
Path = "CN=Users,DC=domain,DC=com" #change to switch based of Users Branch
City = $_.City
Country = $_.Country #NOTE: This Feild must be the 2 digit Country Code, NOT the String Name of athe Country.
department = $_.OrgDepartmentName
Employeeid = $_.EmployeeId
mobile = $_.Mobile
Manager = $_.Manager
Office = $_.Branch
postalCode = $_.PostalCode
POBox = $_.PostOfficeBox
scriptPath = $_.scriptPath
Street = $_.StreetName
Title = $_.Title
}
[System.Collections.ArrayList]$MailAttributesList = #()
foreach ($attribute in $CorrectAttributes.Keys) {
if ($currentAttributes.$attribute -ne $correctAttributes.$attribute) {
$params = #{Identity = $username; $attribute = $correctAttributes.$attribute }
$mailUpdatedAttribute = $CorrectAttributes.$attribute | Out-String
Set-ADUser #params
[void]$MailAttributesList.add("$attribute")
}
else {
Write-Host "$username '$attribute' is correct"
}
$MailAttributesList
$MailAttributesList = #()
}
}
Taken from the part where you test if the user exists and then check which attributes need to be updated, I'd do this:
# personally, I hate using -Properties *
# better to list the properties you are trying to update:
$userAttribs = 'SamAccountName','Name','DisplayName','UserPrincipalName',
'GivenName','Surname','Path','City','Country','Department',
'EmployeeId','MobilePhone','Manager','Office','PostalCode',
'POBox','ScriptPath','Street','Title'
# test if a user with that name already exists
$user = Get-ADUser -Filter "SamAccountName -eq '$username'" -Properties $userAttribs -ErrorAction SilentlyContinue
if ($user) {
# always https://learn.microsoft.com/en-us/powershell/module/addsadministration/set-aduser
# for the correct parameter names!
$CorrectAttributes = #{
SamAccountName = $username
Name = "$firstname $surname"
DisplayName = "$firstname $surname"
UserPrincipalName = "$username#domain.com"
GivenName = $firstname
Surname = $surname
Path = "CN=Users,DC=domain,DC=com" #change to switch based of Users Branch
City = $_.City
Country = $_.Country #NOTE: This Feild must be the 2 digit Country Code, NOT the String Name of athe Country.
Department = $_.OrgDepartmentName
EmployeeId = $_.EmployeeId
MobilePhone = $_.Mobile
Manager = $_.Manager # must be a DistinguishedName, GUID, SID or SamAccountName
Office = $_.Branch
PostalCode = $_.PostalCode
POBox = $_.PostOfficeBox
ScriptPath = $_.scriptPath
Street = $_.StreetName
Title = $_.Title
}
# create a new empty hashtable to store the properties that need updating
$UpdateAttribs = #{}
# capture all changes on behalf of the email body
$changes = foreach ($prop in $CorrectAttributes.Keys) {
if ($user.$prop -ne $CorrectAttributes[$prop]) {
# output an object with the property to be changed for the email
[PsCustomObject]#{
Property = $prop
OldValue = $user.$prop
NewValue = $CorrectAttributes[$prop]
}
# add the value to update in the the $UpdateAttribs hashtable
$UpdateAttribs[$prop] = $CorrectAttributes[$prop]
}
}
if ($changes) { # or do: if ($UpdateAttribs.Count)
# perform the changes
$user | Set-ADUser #UpdateAttribs
# send the email to the admin
$mailParams = #{
To = 'admin#yourcompany.com'
From = 'attributeChanger#yourcompany.com'
Subject = 'User attributes changed'
Body = "Changed AD attributes for user $username`r`n" + ($changes | Format-Table -AutoSize | Out-String)
SmtpServer = 'smtp.yourcompany.com'
UseSsl = $true
Credential = $MailCred
# maybe more parameters go here..
}
Send-MailMessage #mailParams
}
else {
Write-Host "All attributes for user '$username' are correct"
}
}
else {
Write-Warning "User $username does not exist"
}
P.S. The code could be shortened if you have your input CSV file use the correct headers, so they match the property names for Set-ADUser exactly..

Need to update attributes for AD target users such as ObjectSid, msExchMasterAccountSid from a CSV file

I am currently testing the following scenario and looking to automate it defining and validating parameters.
I have put together the following cmdlets to get the script to work calling line-by-line, but what I ultimately like is for this to look at a list of users in a CSV file. From this file, I would like to use two columns with the UserPrincipalName headers, such as:
SourceUser | TargetUser
The idea would be to run a script and replace the following:
#create variables
$sourceUser = "TestUser1#old.domain.com"
$targetUser = "TestUser1#new.domain.com"
$sourceusername,$sourcedomain = $sourceUser -split ("#")
$targetusername,$targetdomain = $targetUser -split ("#")
$SourceAccount = Get-ADUser $sourceusername -server $sourcedomain -Properties objectSid
$TargetAccount = Get-ADUser $targetusername -Server $targetdomain 
#get the objectSid of the source account
$objectSid = $SourceAccount.objectSid
#copy source account objectSid to target account msExchMasterAccountSid
$TargetAccount | Set-ADUser -Replace #{"msExchMasterAccountSid"=$objectSid}
#enable target account
$TargetAccount | Enable-ADAccount
#disable the source account
$SourceAccount | Disable-ADAccount
#move the migrated user into prod OU
$TargetAccount | Move-ADObject -TargetPath "OU=Test,OU=Users,DC=new,DC=domain,DC=com"
I found already a couple of parameters that I believe would help to achieve two things such as the target domain and target OU:
[CmdletBinding()]
Param(
#target domain
[parameter(Mandatory,Position=1)]
[ValidateScript({Get-ADDomain -Identity $_})]
[String]$Domain,
#target OU
[parameter(Position=2)]
[ValidateScript({Get-ADOrganizationalUnit -Identity $_})]
[String]$TargetOu
)
Is there anyone able to help me put all this script together, please? 🙂
Thanks
A draft of a script I develop after sometime:
Clear-Host
#parameters
Import-Module ActiveDirectory
#Start region >>> fake reading in a csv file
$SourceDestinationUsers = #'
SourceUser, DestinationUser
test#source.com, test#destination.com
'# | ConvertFrom-Csv
#endregion >>> fake reading in a CSV file
function Invoke-UserMove
{
[CmdletBinding()]
param()
ForEach ($User in $SourceDestinationUsers)
{
Write-Host 'Processing...'
Write-Host (' SourceUser {0}' -f $User.SourceUser)
Write-Host (' DestinationUser {0}' -f $User.DestinationUser)
Write-Host '__ Source Account __'
$GADU_Params_1 = [ordered]#{
Identity = $User.SourceUser.split('#')[0]
Server = $User.SourceUser.split('#')[1]
Properties = 'objectSid', 'SamAccountName'
}
$GADU_Params_1
$SourceAccount = Get-ADUser #GADU_Params_1
Write-Host '__ Target Account __'
$GADU_Params_2 = [ordered]#{
Identity = $User.DestinationUser.Split('#')[0]
Server = $User.DestinationUser.Split('#')[1]
}
$GADU_Params_2
$TargetAccount = Get-ADUser #GADU_Params_2
Write-Host 'Making changes...'
try
{
$TargetAccount | Set-AdUser -Replace #{'SamAccountName' = $SourceAccount.SamAccountName }
}
catch
{
Write-Host "Accounts have been processed succesfully..."
}
try
{
$TargetAccount | Enable-ADAccount
}
catch
{
Write-Host "Accounts have been processed succesfully..."
}
try
{
$SourceAccount | Disable-ADAccount
}
catch
{
Write-Host "Accounts have been processed succesfully..."
}
try
{
$TargetAccount | Move-ADObject -TargetPath "OU=Test,OU=Users,DC=new,DC=domain,DC=com"
}
catch
{
Write-Host "Accounts have been processed succesfully..."
}
}
Write-Host "Completed"
}
Invoke-UserMove
It worked for me and I did achieve what I needed.
OK, let's say your CSV file contains something like
SourceUser, TargetUser
TestUser1#old.domain.com,Testuser1#new.domain.com
obviously, in reality your csv file would consist of more than one source and target pair.
Now starting with the code you provided, put that in brackets under a foreach loop, and feed the csv data one record at a time through the pipeline. Something like this
Import-csv MyCsvFile.csv |
foreach {
#create variables
$sourceUser = $_.SourceUser
$targetUser = $_.TargetUser
$sourceusername,$sourcedomain = $sourceUser -split ("#")
$targetusername,$targetdomain = $targetUser -split ("#")
$SourceAccount = Get-ADUser $sourceusername -server $sourcedomain -Properties objectSid
$TargetAccount = Get-ADUser $targetusername -Server $targetdomain
#get the objectSid of the source account
$objectSid = $SourceAccount.objectSid
#copy source account objectSid to target account msExchMasterAccountSid
$TargetAccount | Set-ADUser -Replace #{"msExchMasterAccountSid"=$objectSid}
#enable target account
$TargetAccount | Enable-ADAccount
#disable the source account
$SourceAccount | Disable-ADAccount
#move the migrated user into prod OU
$TargetAccount | Move-ADObject -TargetPath "OU=Test,OU=Users,DC=new,DC=domain,DC=com"
}
I didn't fix up the indenting for you, but you get the idea.

Add users into AD group with correct CSV output

I am trying to add an AD group into user profiles based on an OU
I had a similar script working, so tried to modify it and failed. I am guessing it's the " -Identity $_" it maybe, but I am not good enough to debug.
#Create a new class to hold the info for our CSV entry
Class CSVEntry{
[String]$UserName
[String]$GroupName
[String]$TimeStamp
}
#Creating a list to hold the CSV entries
$Results = New-Object 'System.Collections.Generic.List[PSObject]'
#Defined the name of the group here
$GroupName = 'GROUPS NAME'
$ou = 'ou=XX,ou=XX,ou=XX,dc=XX,dc=local'
Get-ADUser -Filter * -SearchBase $ou | ForEach-Object{
#Add the user to the group here
Add-ADPrincipalGroupMembership -MemberOf $GroupName Identity $_
#Write-Host $_.Name - $groupName
#Build a custom CSVEntry object and add it to the list
$newRecord = [CSVEntry]::new()
$newRecord.UserName = $_.Name
$newRecord.GroupName = $groupName
$newRecord.TimeStamp = Get-Date
#Add the new record to the list
$Results.Add($newRecord)
}
#Export the list of CSV entries
$Results | Export-Csv C:\PS\AddADGroupToUsers.csv
errors:
Add-ADPrincipalGroupMembership : A positional parameter cannot be found that accepts argument 'CN=NAME,OU=XX,OU=XX,OU=XX,OU=XX,DC=XX,DC=LOCAL'.
At line:18 char:5
+ Add-ADPrincipalGroupMembership -MemberOf $GroupName Identity $_
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Add-ADPrincipalGroupMembership], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.ActiveDirectory.Management.Commands.AddADPrincipal
GroupMembership
EDIT:
So, the script doesn't actually do any changes, the group doesn't get added to the users. the output on screen is:
WARNING: User is already a member of group XYZ
WARNING: User is already a member of group XYZ
WARNING: User is already a member of group XYZ
UserName GroupName TimeStamp
-------- --------- ---------
shows ok XYZ 14/10/2019 14:50:23
shows ok XYZ 14/10/2019 14:50:23
shows ok XYZ 14/10/2019 14:50:23
All I have changed is the group name to XYZ and username shows ok in the second half. But, shows blank in the top, and I assure you that a) the user isn't already in the group and b) the script isn't adding them
Current tweaked code, warts and all but sanitised:
$groupName = 'GROUP'
$ou = 'setcorrectly'
$cred = Get-Credential -credential dom\usr
$results = Get-ADUser -Filter * -SearchBase $ou -Credential $cred | ForEach-Object {
#Add the user to the group here
try {
Add-ADGroupMember -Identity $groupName -Members $_.DistinguishedName -Credential $cred -ErrorAction Stop
}
catch {
Write-Warning "User $($_.Name) is already a member of group $groupName"
}
# output a PsCustomObject that gets collected in the $results variable
[PsCustomObject]#{
'UserName' = $_.Name
'GroupName' = $groupName
'TimeStamp' = Get-Date
}
}
# output on console
$results | Format-Table -AutoSize
# Export to CSV file
$results | Export-Csv C:\PS\AddADGroupToUsers.csv -NoTypeInformation
Read-Host -Prompt "Press Enter to exit"
CSV output shows the second half of the screen output only, and doesn't say anything is already a member
Below uses Add-ADGroupMember to add user(s) to 1 group instead of Add-ADPrincipalGroupMembership which is used to add 1 user to multiple groups.
It also uses [PsCustomObject]s to output the results, so you don't need to use the Class CSVEntry.
# Define the name of the group here.
# can be either:
# A distinguished name
# A GUID (objectGUID)
# A security identifier (objectSid)
# A Security Account Manager account name (sAMAccountName)
$groupName = '<NAME OF THE GROUP>'
$ou = 'ou=XX,ou=XX,ou=XX,dc=XX,dc=local'
$results = Get-ADUser -Filter * -SearchBase $ou | ForEach-Object {
#Add the user to the group here
$userName = $_.Name
try {
Add-ADGroupMember -Identity $groupName -Members $_.DistinghuishedName -ErrorAction Stop
# output a PsCustomObject that gets collected in the $results variable
[PsCustomObject]#{
'UserName' = $_.Name
'GroupName' = $groupName
'TimeStamp' = Get-Date
}
}
catch {
Write-Warning "User $userName is already a member of group $groupName"
}
}
# output on console
$results | Format-Table -AutoSize
# Export to CSV file
$results | Export-Csv C:\PS\AddADGroupToUsers.csv -NoTypeInformation
Edit
If you want the $results variable to ALSO contain users that are already a member of the group, you could simply move the creation of the [PsCustomObject] below the catch{..} block:
$results = Get-ADUser -Filter * -SearchBase $ou | ForEach-Object {
#Add the user to the group here
$userName = $_.Name
try {
Add-ADGroupMember -Identity $groupName -Members $_.DistinghuishedName -ErrorAction Stop
$status = "User added successfully"
}
catch {
Write-Warning "User $userName is already a member of group $groupName"
$status = "User is already a member"
}
# output a PsCustomObject that gets collected in the $results variable
[PsCustomObject]#{
'UserName' = $userName
'GroupName' = $groupName
'TimeStamp' = Get-Date
'Status' = $status
}
}
Hope that helps

Creating new AD users and adding them to multiple groups from CSV

I have been given the task of creating a school's worth of users (UK Secondary). The PS to create the users from a CSV, what I need to do is add the newly created users to various groups at the same time.
The code I am using is as follows
$DCName = '<DC FQDN>'
Import-Csv -Path "D:\Import.csv" |
ForEach-Object {
$Displayname = $_.'FirstName' + " " + $_.'LastName'
$UPN = $_.'UPN'
$GroupName = $_.'GroupName'
$Prop = #{
Name = $Displayname
DisplayName = $_.'FirstName' + " " + $_.'LastName'
GivenName = $_.'FirstName'
Surname = $_.'LastName'
UserPrincipalName = $UPN
EmailAddress = $UPN
SamAccountName = $_.'SAM'
AccountPassword = (ConvertTo-SecureString $_.'Password' -AsPlainText -Force)
Enabled = $true
Path = $_.'OU'
ChangePasswordAtLogon = $false
Title = $_.'JobTitle'
StreetAddress = $_.'Street'
City = $_.'Town'
State = $_.'County'
PostalCode = $_.'PostCode'
OfficePhone = $_.'Telephone'
Company = $_.'Company'
Department = $_.'Department'
HomeDrive = $_.'HomeDrive'
HomeDirectory = $_.'Home-Directory'
OtherAttributes = #{
'extensionAttribute1'= $_.'ExtendedAttribute1';
'extensionAttribute2'= $_.'ExtendedAttribute2';
'extensionAttribute14'= $_.'ExtendedAttribute14';
'extensionAttribute15'= $_.'ExtendedAttribute15';
'proxyAddresses' = "SMTP:" + $UPN;}
Server = $DCName
}
New-ADUser #prop
Add-ADGroupMember -Identity $GroupName -Members $_.'SAM'
}
The user gets created with all properties correctly set. It fails with the following error
Add-ADGroupMember : Cannot find an object with identity: 'Test.User' under: 'DC=AD,DC=example,DC=uk'.
At C:\Scripts\NewUserFromCSV2.ps1:47 char:10
+ Add-ADGroupMember -Identity $GroupName -Members $_.'SAM'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Test.USer:ADPrincipal) [Add-ADGroupMember], ADIdentityNotFoundException
+ FullyQualifiedErrorId : SetADGroupMember.ValidateMembersParameter,Microsoft.ActiveDirectory.Management.Commands.AddADGroupMember
It looks like the Add-ADGroupMember command can't find the user that has just been created, however, if that is the case I don't understand why.
Also at the moment, my CSV has only one group in the 'GroupName', what would be the best way to add the user to multiple groups? e.g. School-All-Staff, Teaching-Staff, Science-Teachers etc.
Thanks in advance for any help received.
As it's a bulk operation, I would just split the user creation from the group membership.
Create all the users first, then add them to the groups:
$DCName = '<DC FQDN>'
$Users = Import-Csv -Path "D:\Import.csv"
$Users | ForEach-Object {
$Displayname = $_.'FirstName' + " " + $_.'LastName'
$UPN = $_.'UPN'
$Prop = #{
## properties as per original code ##
}
New-ADUser #prop
}
$Users | ForEach-Object {
$GroupName = $_.'GroupName'
Add-ADGroupMember -Identity $GroupName -Members $_.'SAM'
}
The to add the users to multiple groups:
If you've got a semicolon separated list of the groups in GroupName eg
School-All-Staff;Teaching-Staff;Science-Teachers
Split will convert this to an array then you can loop through them:
$_.'GroupName' -split ';' | ForEach-Object {
Add-ADGroupMember $_ –Member $user.'SAM'
}
(edit: updated to semicolon as you have a csv source)
I got it working as a combined script in the end and added in error checking for pre-existing users, existing staff often move to the new school that is being added to the Trust prior to its addition to our AD and get included in the list of users to create.
Also added log file creation to record newly created users and list those whose SAMAccount name already exists so we can check to see if the user does need creating or if they need moving from another School OU.
This is my final code:
#Get deafult variables to tidy up created variables at the end
$ExistingVariables = Get-Variable | Select-Object -ExpandProperty Name
#New User Code Starts Here>
#Variables not set by CSV
#Set DC name to update - prevents errors due to replication delay
$DCName = '<DC FQDN>'
#Create log files
"Users Exist in AD" | Out-File -FilePath "D:\Logs\ExistingUsers-$(get-date -f yyyyMMdd).txt" -Append
"New Users Created" | Out-File -FilePath "D:\Logs\NewUsers-$(get-date -f yyyyMMdd).txt" -Append
#Specify path and file to import
Import-Csv -Path "D:\Import.csv" |
#Iterate through each row in the CSV
ForEach-Object {
#Set per object variables from fields in the CSV
$DisplayName = $_.'FirstName' + " " + $_.'LastName'
$UPN = $_.'UPN'
$GroupName1 = $_.'GroupName1'
$GroupName2 = $_.'GroupName2'
$GroupName3 = $_.'GroupName3'
$GroupName4 = $_.'GroupName4'
$SAM = $_.'SAM'
$Password = $_.'Password'
$SAMTest = Get-ADUser -Filter {(sAMAccountName -eq $SAM)} -Server $DCName
#Splatting Hash Table holds all user attribute properties set in the CSV
$Prop = #{
Name = $DisplayName
DisplayName = $DisplayName
GivenName = $_.'FirstName'
Surname = $_.'LastName'
UserPrincipalName = $UPN
EmailAddress = $UPN
SamAccountName = $_.'SAM'
AccountPassword = (ConvertTo-SecureString $_.'Password' -AsPlainText -Force)
Enabled = $true
Path = $_.'OU'
ChangePasswordAtLogon = $false
Title = $_.'JobTitle'
StreetAddress = $_.'Street'
City = $_.'Town'
State = $_.'County'
PostalCode = $_.'PostCode'
OfficePhone = $_.'Telephone'
Company = $_.'Company'
Department = $_.'Department'
OtherAttributes = #{
'extensionAttribute1'= $_.'ExtendedAttribute1';
'extensionAttribute2'= $_.'ExtendedAttribute2';
'extensionAttribute14'= $_.'ExtendedAttribute14';
'extensionAttribute15'= $_.'ExtendedAttribute15';
'proxyAddresses' = "SMTP:" + $UPN;}
Server = $DCName
}
#Check if SAMAccount name exists in AD and skip existing users
if ($SAMTest -ne $Null)
{
#Get UPN property of the pre-existing user
$Exist = Get-ADUser -Filter {(sAMAccountName -eq $SAM)} -Properties 'userprincipalname'
#write UPN value to variable
$ExistUPN = $Exist.userprincipalname
#Update log of pre-existing users
"$DisplayName exists with email $ExistUPN" | Out-File -FilePath "D:\Logs\ExistingUsers-$(get-date -f yyyyMMdd).txt" -Append
#Write to screen
Write-Host "$DisplayName already exists in AD" -ForegroundColor Red
}
else
{
#Create new user with the attribute properties collected above
New-ADUser #prop
#Check if group fields in CSV were populated, if true add user to group, if false skip
if ($_.'GroupName1'){Add-ADGroupMember -Identity $_.'GroupName1' -Members $_.'SAM' -Server $DCName}
if ($_.'GroupName2'){Add-ADGroupMember -Identity $_.'GroupName2' -Members $_.'SAM' -Server $DCName}
if ($_.'GroupName3'){Add-ADGroupMember -Identity $_.'GroupName3' -Members $_.'SAM' -Server $DCName}
if ($_.'GroupName4'){Add-ADGroupMember -Identity $_.'GroupName4' -Members $_.'SAM' -Server $DCName}
#Update New user log
"$UPN" | Out-File -FilePath "D:\Logs\NewUsers-$(get-date -f yyyyMMdd).txt" -Append
#Write to screen
Write-Host "User $SAM created at $((Get-Date).ToString('hh:mm'))" -ForegroundColor Green
}
}
#End Of New User Code
#Remove variables set by script - keeps PS memory space tidy
$NewVariables = Get-Variable | Select-Object -ExpandProperty Name | Where-Object {$ExistingVariables -notcontains $_ -and $_ -ne "ExistingVariables"}
if ($NewVariables)
{
Write-Host "Removing the following variables:`n`n$NewVariables"
Remove-Variable $NewVariables
}
else
{
Write-Host "No new variables to remove!"
}
I used the bit about clearing up variables because the values seemed to persist if the PowerShell session remained open and it was causing odd things to happen. I also removed the home drive attributes because the file server specified hasn't been implemented yet but management still wants the users in AD now.
For reference my import.csv looks like this
FirstName,LastName,UPN,SAM,Password,OU,JobTitle,Street,Town,County,PostCode,Telephone,Company,Department,ExtendedAttribute1,ExtendedAttribute2,ExtendedAttribute14,ExtendedAttribute15,GroupName1,GroupName2,GroupName3,GroupName4
Test,User,Test.Users#domain.uk,Test.User,,"OU=Admin Staff,OU=User Resources,OU=School,OU=Trust Schools,DC=AD,DC=Trust,DC=org",,Street Name,TownName,County,AA11 1AA,116123,Name Of School,Name Of Trust,,Staff,,,AllStaffGroup,AdminStaffGroup,SpecialPermissionsGroup,Group4