I am having an issue with a script that I have been modifying to create AD accounts from a CSV file that gets uploaded on a daily basis. I'll start off by saying that I did not write this script, I found it online and it is available for anyone to use, I
have only been modifying it to fit our needs. Also, I am really just getting into using PS more and more and definitely not a pro by any stretch, hence the reason I am here.
I'll try not to make this incredibly long and boring but want you to understand the scripts purpose and where I am having problems at. The script should look for a CSV file and use that file to create/update/disable AD user accounts based on different fields.
The script checks for duplicate SAM accounts as part of this process and has a method to come up with a different username if there is a duplicate. Also, based on the "Status" field the script should either enable or disable an existing user account.
The main issue I am currently having is that the account will get created, everything seems to work properly except all accounts will be disabled during the running of the script, regardless of the status field. If I comment out those lines of the script
the account will get created and be enabled like it should be so I'm not sure what I am missing.
Also, when a duplicate SAMaccountname is found, the script should come up with a unique name based on defined rules but that does not seem to work properly as I get messages during the New-ADuser command that it fails due to a user with that name already
existing.
I will post the code below and any help or guidance I could get would be greatly appreciated. I am not asking for someone to re-write this and make it work, simply point me in the right direction would be great. Again, I am not great with PS as of yet but
do feel I at least understand the flow of this script, just can't seem to understand where it is going wrong at.
# Import the Active Directory functions
Import-Module ActiveDirectory
#Setup some hashtables
#For each school code in your SIS, map it to the appropriate fileserver in the format "schoolcode" = "servername";
#$server = @{"1001" = "fscluster1"; "1002" = "SchoolB-FS"; "1003" = "SchoolC-FS"}
#If you're using standardized abbreviations anywhere (perhaps your groups are named like like SITEA-Students, SITEB-Students etc) It's useful to create a map of those abbreviations
$siteabbr = @{"1001" = "2021"; "1002" = "SITEB"; "1003" = "SITEC"}
#Create a map of codes to Active Directory OUs where students should be moved/created etc. Student grade to grad year mapping.
$orgunits = @{"12" = "2019"; "11" = "2020"; "10" = "2021"; "09" = "2022"; "08" = "2023"; "07" = "2024"; "06" = "2025"; "05" = "2026"; "04" = "2027"; "03" = "2028"; "02" = "2029"; "01" = "2030"; "K" = "2031"; "PK" = "2032"}
#Create a map of grades to email distribution groups.
$emailgroup = @{"12" = "Seniors"; "11" = "Juniors"; "10" = "Sophmores"; "9" = "Freshmen"; "PK" = "PK"}
# Import the Data - This is based on using the accompanying SISsync.sql file to extract data from PowerSchool and expects a tab delimited file, if you're using a CSV from another system or autosend, change `t to , (or omit the delimiter option entirely) and modify the headers appropriately
$sisfile = Import-Csv -delimiter "`t" -Path "C:\TEMP\AD_SYNC\DATA\cts export.txt" -header "grade","givenName","sn","lunchpin","studentid","status"
#Start Processing per file line
foreach ($sisline in $sisfile) {
#Set the username example below is gradyear+firstinitial+lastname. If a duplicate is found the format will be gradyear+firstthreeletters+lastname.
$sisline.givenname | ForEach-Object {$firstinitial = $_[0]}
$givenname = $sisline.givenname
# $dup variable gets the first three letters of the students first name to use if a duplicate SAMaccountname is found.
$dup = $sisline.givenname.Substring(0,3)
$duplicate = $orgunits.Get_Item($sisline.Grade) + $dup + $sisline.sn
$sAMAccountName = $orgunits.Get_Item($sisline.Grade) + $firstinitial + $sisline.sn
#tidy up samaccountName to make it more valid (no spaces, double periods or apostrophies. Helpful for when there's data entry 'issues' in your source
$sAMAccountName = $sAMAccountName.replace(" ","")
$sAMAccountName = $sAMAccountName.replace("..",".")
$sAMAccountName = $sAMAccountName.replace("'","")
$sAMAccountName = $sAMAccountName.replace("-","")
#Truncate to 19 characters
#$sAMAccountName = $sAMAccountName.substring(0,19)
#Set the displayname for the account in AD example below is firstname space lastname
$name = $sisline.givenName + " " + $sisline.sn
#Set a password for the account, example below takes their Lunch PIN (LunchPIN) and assigns it as their initial password
$pass = "wildcats" + $sisline.lunchPIN
$password = ConvertTo-SecureString -AsPlainText $pass -Force
#Set the UPN for the account for most instances, should be AD Account name + @AD.FQDN. Need to change for each domain!
$userPrincipalName = $sAMAccountName + "@slater.local"
#Set the mail attribute for the account (if desired, usually helpful if you're synchronizing to Google Apps/Office 365)
$mail = $sAMAccountName + "@testschool.net"
#Set name attributes
$givenName = $sisline.givenName
$sn = $sisline.sn
#Set status variable (if account gets enabled or disabled) Status is determined whether or not there is a value in this field. Only
#will have a value if the student has withdrawn from school.
$status = $sisline.status
#Store student ID in AD's "EmployeeID" attribute
$employeeID = $sisline.studentid
#Optional location attributes, helpful if syncing to Moodle via LDAP
$c = "US"
$co = "United States"
$l = $orgunits.Get_Item($sisline.Grade)
#Optional other attribute population we set these because they're easy to view with the MMC taskpad we push to secretaries to allow them to reset passwords
$company = $orgunits.Get_Item($sisline.Grade)
$physicalDeliveryOfficeName = $sisline.grade
$description = $orgunits.Get_Item($sisline.Grade)
$comment = $sAMAccountName + "@slater.local"
#Create a hashtable of all the "otherattributes" this is used when we create/update the user
$otherAttributes = @{'userPrincipalName' = "$userPrincipalName"; 'mail' = "$mail"; 'comment' = "$comment"; 'givenName' = "$givenName"; 'sn' = "$sn"; 'employeeID' = "$employeeID"; 'employeeNumber' = "$pass"; 'c' = "$c"; 'l' = "$l"; 'company' = "$company"; 'physicalDeliveryOfficeName' = "$physicalDeliveryOfficeName"; 'description' = "$description"}
#recast description as a string because AD commands require it and it gets converted to int if it's all numeric.
$otherAttributes.description = [string]$otherAttributes.description
#set the path variable to the OU the student should end up in. In the example below the AD OU Structure is Slater -> Test -> Students -> 2021
$path = "OU=" + $company + ",OU=STUDENT,OU=USERS,OU=MANAGED,DC=slater,DC=local"
#Check if student exists
#THIS IS WHERE IT GETS TERRIBLY SLOW IF YOU HAVEN'T ADDED EMPLOYEEID TO THE LIST OF INDEXED AD ATTRIBUTES. STRONGLY CONSIDER THIS.
$user = Get-ADUser -Filter {employeeID -eq $employeeID}
if ($user -eq $null) {
#student doesn't exist, create them
#find a valid username
#This is probably the most inelegant backwards way of doing this, but it works. Feel free to improve
$i = 1
$sAMSearch = $sAMAccountName
while ((Get-ADUser -Filter {sAMAccountName -eq $sAMSearch}) -ne $null) {
$sAMSearch = $duplicate
$i++
}
$i--
if ($i -ne 0) {
#name was taken, update constants to reflect new name formart gradyearfirstthreelastname
$sAMAccountName = $sAMSearch
$otherAttributes.Set_Item("userPrincipalName", $sAMAccountName + "@slater.local")
$otherAttributes.Set_Item("mail", $sAMAccountName + "@testschool.net")
$otherAttributes.Set_Item("comment", $sAMAccountName + "@testschool.net")
#$name = $name + $i
}
#create user using $sAMAccountName and set attributes and assign it to the $user variable
New-ADUser -sAMAccountName $sAMAccountName -Name $name -Path $path -otherAttributes $otherAttributes -Enable $true -AccountPassword $password -CannotChangePassword $true -PasswordNeverExpires $true
$user = Get-ADUser -Filter {employeeID -eq $employeeID}
} elseif (($user.Surname -ne $sn) -or ($user.givenName -ne $givenName)) {
#The first or last names were changed in the import source, need to make some changes to the user
#find a valid username
#This is probably the most inelegant backwards way of doing this, but it works. Feel free to improve
$i = 1
$sAMSearch = $sAMAccountName
while ((Get-ADUser -Filter {sAMAccountName -eq $sAMSearch}) -ne $null) {
$sAMSearch = $duplicate
$i++
}
$i--
if ($i -ne 0)
#need to update Name, sAMAccountName, UPN and email because of name collison
{
$sAMAccountName = $sAMSearch
$otherAttributes.Add("sAMAccountName", $sAMAccountName)
$otherAttributes.Set_Item("userPrincipalName", $sAMAccountName + "@slater.local")
$otherAttributes.Set_Item("mail", $sAMAccountName + "@testschool.net")
$otherAttributes.Set_Item("comment", $sAMAccountName + "@testschool.net")
$name = $name
}
Rename-ADObject -Identity $user $name
#need to re-key user variable after rename
$user = Get-ADUser -Filter {employeeID -eq $employeeID}
#Update AD attributes to reflect changes
Set-ADUser -Identity $user -replace $otherAttributes -SamAccountName $sAMAccountName
} else {
#Update AD Attributes for existing user whos name hasn't changed. Unset anything usernamebased first since the username hasn't changed
$otherAttributes.Remove("userPrincipalName")
$otherAttributes.Remove("mail")
$otherAttributes.Remove("comment")
Set-ADUser -Identity $user -replace $otherAttributes
}
#reset the samaccountname variable to what it currently queries out of AD as, probably not necessary
$sAMAccountName = $user.SamAccountName
#check to see if the DN of the user contains the school name, if not, move it to the correct location
$properdn = "OU=$company,"
write-host $properdn
if ($user.DistinguishedName -notlike "*$properdn*")
{
Move-ADObject -Identity $user -TargetPath $path
$user = Get-ADUser -Filter {employeeID -eq $employeeID}
}
# $user = Get-ADUser -Filter {samaccountname -eq $samaccountname}
write $user
#Enable or disable a user account. This is determined by whether or not there is a value in the
#withdrawal date field. If there is a value student is no longer active and should be disabled
#if there is no value student is active and should be enabled.
if ($status -ne " "){
Disable-ADAccount -Identity $user}
elseif ($status -like " "){
Enable-ADAccount -Identity $user}
#Check to see if folders exist on proper server, if not, create them and set permissions.
#Used to dynamically pick fileserver based on certain field - $servername = $server.Get_Item($sisline.grade)
$servername = "fscluster1"
#The example below assumes student home folders exist on \\fileserver\student$\username structure
$homepath = "\\" + $servername + "\student$\" + $sAMAccountName
if ((Test-Path ($homepath)) -ne $true)
{
#create folder and set permissions
#Change DOMAIN below with your AD Domain
New-Item -ItemType directory -Path $homepath
$acl = Get-Acl $homepath
$permission = "slater.local\$sAMAccountName","Modify","ContainerInherit,ObjectInherit","None","Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$acl.SetAccessRule($accessRule)
$acl | Set-Acl $homepath
}
#A quick 100ms pause to make sure the folder has been created and the permissions applied. you may be able to dial that back or remove it entirely
Start-Sleep -m 100
#Set the users homedrive
Set-ADUser -Identity $user -HomeDirectory $homepath -HomeDrive "H:"
#Add user to site student group and grad year group also a good place to add any other groups you may require
#This assumes a security group with the site abbreviation-Students exists and a group called Grad#### exists
#It doesn't check to see if the user is already a part of these groups, so it will often print an error stating it can't add them because they already exist
$studentgroup1 = $orgunits.Get_Item($sisline.grade)
#Add students to the correct email distribution group based on grade level.
$studentgroup2 = $emailgroup.Get_Item($sisline.grade)
#$gradgroup = "Grad" + $description
Add-ADGroupMember $studentgroup1 $user
#Add-ADGroupMember $studentgroup2 $user
#Add-ADGroupMember ALL_STUDENT $user
}
#rename.ps1
#Change filename to whatever file needs to be renamed.
$fileName = "C:\TEMP\AD_SYNC\DATA\cts export.txt"
# Check the file exists
# if (-not(Test-Path $fileName))
# {break}
# Display the original name
"Original filename: $fileName"
$fileObj = get-item $fileName
# Get the date
$DateStamp = get-date -uformat "%Y-%m-%d@%H-%M-%S"
$extOnly = $fileObj.extension
if ($extOnly.length -eq 0) {
$nameOnly = $fileObj.Name
rename-item "$fileObj" "$nameOnly-$DateStamp"
}
else {
$nameOnly = $fileObj.Name.Replace( $fileObj.Extension,'')
rename-item "$fileName" "$nameOnly-$DateStamp$extOnly"
}
# Display the new name
#"New filename: $nameOnly-$DateStamp$extOnly"
#Sorts files by creation date, skips the top twenty newest files and deletes any older than the top twenty. Folder path and number of
#skipped files can be modified to fit your needs.
Get-ChildItem C:\TEMP\AD_SYNC\DATA\ -Recurse| Where-Object{-not $_.PsIsContainer}| Sort-Object CreationTime -desc|
Select-Object -Skip 10| Remove-Item -Force
Here is some of the error messages I get when running the PS script:
PS C:\TEMP\AD_SYNC> C:\TEMP\AD_SYNC\SISsync.ps1
New-ADUser : The server is unwilling to process the request
At C:\TEMP\AD_SYNC\SISsync.ps1:92 char:3
+ New-ADUser -sAMAccountName $sAMAccountName -Name $name -Path ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (CN=GivenName SN...slater,DC=local:String) [New-ADUse
r], ADException
+ FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Comman
ds.NewADUser
OU=,
Move-ADObject : Cannot validate argument on parameter 'Identity'. The argument is null. Provide a
valid value for the argument, and then try running the command again.
At C:\TEMP\AD_SYNC\SISsync.ps1:137 char:27
+ Move-ADObject -Identity $user -TargetPath $path
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Move-ADObject], ParameterBindingValidationExcepti
on
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.ActiveDirectory.Manageme
nt.Commands.MoveADObject
Disable-ADAccount : Cannot validate argument on parameter 'Identity'. The argument is null.
Provide a valid value for the argument, and then try running the command again.
At C:\TEMP\AD_SYNC\SISsync.ps1:152 char:33
+ Disable-ADAccount -Identity $user}
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Disable-ADAccount], ParameterBindingValidationExc
eption
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.ActiveDirectory.Manageme
nt.Commands.DisableADAccount
Set-ADUser : Cannot validate argument on parameter 'Identity'. The argument is null. Provide a
valid value for the argument, and then try running the command again.
At C:\TEMP\AD_SYNC\SISsync.ps1:180 char:23
+ Set-ADUser -Identity $user -HomeDirectory $homepath -HomeDrive "H ...
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Set-ADUser], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.ActiveDirectory.Manageme
nt.Commands.SetADUser