Reputation: 747
*** COMPLETE REFRESH ***
I have made so many changes trying to figure this out, I decided to just refresh this question and post the entire script in hopes of finding a solution. I apologize for the length of the post. ANY info on what I am doing wrong would be a blessing. I have a lot of other stuff going on and keep circling back to this only to hit the same roadblocks. Thanks group!
With this current iteration, I am getting the following output/error now:
Transcript started, output file is C:\SftpRoot\loginid\logs\ou.log
Students OU already exists. Skipping...
ES OU already exists. Skipping...
1 OU already exists. Skipping...
2 OU already exists. Skipping...
3 OU already exists. Skipping...
4 OU already exists. Skipping...
NW OU already exists. Skipping...
1 OU already exists. Skipping...
2 OU already exists. Skipping...
3 OU already exists. Skipping...
4 OU already exists. Skipping...
SR OU already exists. Skipping...
1 OU already exists. Skipping...
2 OU already exists. Skipping...
3 OU already exists. Skipping...
4 OU already exists. Skipping...
EC OU already exists. Skipping...
PK OU already exists. Skipping...
K OU already exists. Skipping...
PI OU already exists. Skipping...
5 OU already exists. Skipping...
6 OU already exists. Skipping...
MS OU already exists. Skipping...
7 OU already exists. Skipping...
8 OU already exists. Skipping...
HS OU already exists. Skipping...
9 OU already exists. Skipping...
10 OU already exists. Skipping...
11 OU already exists. Skipping...
12 OU already exists. Skipping...
802.1x Staff Group already exists. Skipping...
802.1x Student Group already exists. Skipping...
Domain Admins Group already exists. Skipping...
Domain Users Group already exists. Skipping...
Importing the data. Can be long, please wait.
DistinguishedName : CN=Jon Doe,OU=11,OU=HS,OU=Students,DC=devdc,DC=com
Enabled : True
GivenName : Jon
Name : Jon Doe
ObjectClass : user
ObjectGUID : 1ef7e9b2-5e77-4dc7-8275-62a14271b8f9
SamAccountName : 11506
SID : S-1-5-21-1720126610-2895624269-3985084094-8087
Surname : Doe
UserPrincipalName : [email protected]
Error!
UpdateUser : Error updating user: ,
At C:\SftpRoot\loginid\Student-Import-To-AD.ps1:234 char:13
+ UpdateUser $StudentParameters
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,UpdateUser
Here is the full script as of right now...
<#
Set our Preference Variables and
create the transcript log
#>
$ErrorActionPreference="SilentlyContinue"
Stop-Transcript | Out-Null
$ErrorActionPreference = "Continue"
Start-Transcript -path "C:\SftpRoot\loginid\logs\ou.log"
<#
Adding old fallbacks just in case
#>
Import-Module ActiveDirectory -ErrorAction SilentlyContinue
<#
VARIABLES
Make all options easy to edit and maintain
#>
$CSVFilePath = "C:\SftpRoot\loginid\student-ad.csv"
$Schools = @(
[pscustomobject]@{Name='ES';Id='105';Grades=@('1','2','3','4')}
[pscustomobject]@{Name='NW';Id='120';Grades=@('1','2','3','4')}
[pscustomobject]@{Name='SR';Id='115';Grades=@('1','2','3','4')}
[pscustomobject]@{Name='EC';Id='130';Grades=@('PK','K')}
[pscustomobject]@{Name='PI';Id='125';Grades=@('5','6')}
[pscustomobject]@{Name='MS';Id='510';Grades=@('7','8')}
[pscustomobject]@{Name='HS';Id='705';Grades=@('9','10','11','12')}
)
$Groups = '802.1x Staff','802.1x Student','Domain Admins','Domain Users' # Add Groups in that the students should be added to.
$SubDomain = 'student' # SubDomain is used for email only.
<#
Name: SchoolName
Params: $schoolID
Instructions: Call function and pass a school ID
in order to get the readable name.
#>
Function SchoolName {
[CmdletBinding()]
param(
[int] $passedID
)
switch($passedID) {
130{return "EC"}
105{return "ES"}
115{return "SR"}
120{return "NW"}
125{return "PI"}
510{return "MS"}
705{return "HS"}
}
} # END SchoolName
<#
Name: GradeLevel
Params: $passedID
Instructions: Call function and pass a grade level
in order to get the readable name.
#>
Function GradeLevel {
[CmdletBinding()]
param(
[string] $passedID
)
switch($passedID) {
"0"{return "K"}
"-1"{return "PK"}
default{return "$passedID"}
}
} # END GradeLevel
<#
Name: CreateOUs
Params: NA
Instructions: Call function to verify and create
Organizational Units in AD.
#>
Function CreateOUs() {
[CmdletBinding()]
# Create Inner Variables
$DomainPath = 'DC='+$env:USERDNSDOMAIN.Substring(0,$env:USERDNSDOMAIN.Length-4).ToLower()+',DC='+$env:USERDNSDOMAIN.Substring($env:USERDNSDOMAIN.Length-3).ToLower()
if(![adsi]::Exists("LDAP://OU=Students,$DomainPath")){New- ADOrganizationalUnit Students -ProtectedFromAccidentalDeletion $True; Write-Host "Students OU being created." -foregroundcolor Green}else{Write-Host "Students OU already exists. Skipping..." -foregroundcolor Yellow}
# Check for the schools OU paths
$Schools | ForEach-Object {
$SchoolPath = "OU=Students,$DomainPath"
$SchoolName = $_.'Name'
if(![adsi]::Exists("LDAP://OU=$SchoolName,$SchoolPath")){New-ADOrganizationalUnit $SchoolName -Path $SchoolPath -ProtectedFromAccidentalDeletion $False; Write-Host "$SchoolName OU being created." -foregroundcolor Green}else{Write-Host "$SchoolName OU already exists. Skipping..." -foregroundcolor Yellow}
$GradePath = "OU=$SchoolName,$SchoolPath"
ForEach($Grade in $_.'Grades'){
if(![adsi]::Exists("LDAP://OU=$Grade,$GradePath")){New-ADOrganizationalUnit $Grade -Path $GradePath -ProtectedFromAccidentalDeletion $False; Write-Host "$Grade OU being created." -foregroundcolor Green}else{Write-Host "$Grade OU already exists. Skipping..." -foregroundcolor Yellow}
}
}
} CreateOUs # END CreateOUs
<#
Name: CreateGroups
Params: NA
Instructions: Call function to verify and create
Organizational Groups in AD.
#>
Function CreateGroups {
[CmdletBinding()]
# Check for and create the Groups in the provided array above
$Groups | ForEach-Object {
# Create Path variable
$gPath = 'CN=Users,DC='+$env:USERDNSDOMAIN.Substring(0,$env:USERDNSDOMAIN.Length-4).ToLower()+',DC='+$env:USERDNSDOMAIN.Substring($env:USERDNSDOMAIN.Length-3).ToLower()
$gName = $_
if(![adsi]::Exists("LDAP://CN=$gName,$gPath")){New-ADGroup -GroupCategory "Security" -GroupScope "Global" -Name "$gName" -Path "$gPath" -SamAccountName "$gName"; Write-Host "$gName Group is being created." -foregroundcolor Green}else{Write-Host "$gName Group already exists. Skipping..." -foregroundcolor Yellow}
}
} CreateGroups # END CreateGroups
<#
Name: CreateUser
Params: [array]PassedUser
Instructions: Call function and pass in a User object
which has all values needed to create New-ADUser.
#>
Function CreateUser {
[CmdletBinding()]
param(
[array] $PassedUser
)
try {
New-ADUser @PassedUser
} catch {
Write-Error "Error creating user: $($PassedUser.lname), $($PassedUser.fname)"
}
} # END CreateUser
<#
Name: UpdateUser
Params: [array]PassedUser
Instructions: Call function and pass in a User object
which has all values needed to update Set-ADUser.
#>
Function UpdateUser {
[CmdletBinding()]
param(
[array]$PassedUser
)
try {
Get-ADUser -Identity $($PassedUser.SamAccountName) | `
Set-ADUser -Identity $($PassedUser.SamAccountName) -ChangePasswordAtLogon $False -Description "$($user.studentID)" -DisplayName "$($user.lname+", "+$user.fname)" -Mail "$($StudentEmail)" -Enabled $True -GivenName "$($user.fname)" -PasswordNeverExpires $True -SamAccountName "$($user.studentID)" -Surname "$($user.lname)" -UserPrincipalName "$($user.studentID+"@"+$env:USERDNSDOMAIN.ToLower())"
} catch {
Write-Host "Error!" -ForegroundColor Red
Write-Error "Error updating user: $($PassedUser.lname), $($PassedUser.fname)"
}
} # END UpdateUser
<#
Begin the real work here.
Above is variable declarations and
building operations for the AD-Groups
and the AD-OUs.
#>
#Store the data from Users.csv in the $Users variable
Write-Host 'Importing the data. Can be long, please wait.'
If ([System.IO.File]::Exists($CSVFilePath)) {
$Users = Import-csv $CSVFilePath `
-Header lname, fname, mname, studentID, gradeLevel, schoolID
#$Users |
#Select *, @{Name='schoolName';Expression={SchoolName $_.schoolID}} | `
#Select *, @{Name='actualGrade';Expression={GradeLevel $_.gradeLevel}} | `
#Sort-Object -Property "schoolID", "gradeLevel", "lname", "fname", "mname"
foreach($user in $Users) {
<#
Check for inclusion of a Middle Name.
Build appropriate variables needed.
#>
$HasMiddleName = ![string]::IsNullOrEmpty($user.mname)
If ($HasMiddleName) {
$global:Password = ($user.fname.substring(0,1) + $user.mname.substring(0,1) + $user.lname.substring(0,1) + $user.studentID)
$global:MiddleInitial = $user.mname.substring(0,1)
}else{
$global:Password = ($user.fname.substring(0,1) + $user.lname.substring(0,1) + $user.studentID)
$global:MiddleInitial = $Null
}
<#
Check for inclusion of a SubDomain.
Build out appropriate variables needed.
#>
If (![string]::IsNullOrEmpty($SubDomain)){
$global:StudentEmail = ($user.studentID+"@"+$SubDomain+"."+$env:USERDNSDOMAIN.Substring(0,$env:USERDNSDOMAIN.Length-4).ToLower()+"."+$env:USERDNSDOMAIN.Substring($env:USERDNSDOMAIN.Length-3).ToLower())
} else {
$global:StudentEmail = ($user.studentID+"@"+$env:USERDNSDOMAIN.Substring(0,$env:USERDNSDOMAIN.Length-4).ToLower()+"."+$env:USERDNSDOMAIN.Substring($env:USERDNSDOMAIN.Length-3).ToLower())
}
<#
Check for existing user.
Either update an existing or create a new
#>
try {
Get-ADUser -Identity $user.studentID
# Account exists. Update name fields.
$StudentParameters = @{
ChangePasswordAtLogon = $False
Description = "$($user.studentID)"
DisplayName = "$($user.lname+", "+$user.fname)"
Mail = "$($StudentEmail)"
Enabled = $True
GivenName = "$($user.fname)"
PasswordNeverExpires = $True
SamAccountName = "$($user.studentID)"
Surname = "$($user.lname)"
UserPrincipalName = "$($user.studentID+"@"+$env:USERDNSDOMAIN.ToLower())"
}
# Set trouble properties this way instead.
if( ($Null -ne $MiddleInitial) -and ("" -ne $MiddleInitial) ) {
$StudentParameters.Add("Initials", "$MiddleInitial")
}
UpdateUser $StudentParameters
} catch [Microsoft.ActiveDirectory.Management.ADIdentityResolutionException] {
# Account does NOT exist. Create new user.
$StudentParameters = @{
ChangePasswordAtLogon = $False
Description = $($user.studentID)
DisplayName = ($user.lname+", "+$user.fname)
EmailAddress = $($StudentEmail)
Enabled = $True
GivenName = $($user.fname)
Name = $($user.studentID)
PasswordNeverExpires = $True
Path = "OU=$user.actualGrade,OU=$user.schoolName,OU=Students,DC="+$env:USERDNSDOMAIN.Substring(0,$env:USERDNSDOMAIN.Length-4).ToLower()+",DC="+$env:USERDNSDOMAIN.Substring($env:USERDNSDOMAIN.Length-3).ToLower()
SamAccountName = S($user.studentID)
Surname = $($user.lname)
UserPrincipalName = ($user.studentID+"@"+$env:USERDNSDOMAIN.ToLower())
}
# Set trouble properties this way instead.
if( ($Null -ne $MiddleInitial) -and ("" -ne $MiddleInitial) ) {
$StudentParameters.Add("Initials", "$MiddleInitial")
}
CreateUser $StudentParameters
}
}
}
<#
Let's wrap it up now!!!
Give the date (should be the same)
and stop the transcript log.
#>
# Log the date
Get-Date
# Stop the transcript log
Stop-Transcript
Upvotes: 0
Views: 1380
Reputation: 5122
You cannot splat @StudentParameters
into parameters/arguments for Function UpdateUser
. Essentially what splatting is doing is this:
UpdateUser -ChangePasswordAtLogon $False -Description $($user.studentID) `
-DisplayName ($user.lname+", "+$user.fname) -Mail $($StudentEmail) `
-Enabled $True -GivenName $($user.fname) -Initials $($MiddleInitial) `
-PasswordNeverExpires $True -SamAccountName $($user.studentID) `
-Surname $($user.lname) -UserPrincipalName ($user.studentID+"@"+$env:USERDNSDOMAIN.ToLower())
The UpdateUser function has none of these parameters. Initials just happens to be the first one it encounters and errors on. It says exactly this in the error message.
UpdateUser : A parameter cannot be found that matches parameter name 'Initials'.
At C:\SftpRoot\loginid\Student-Import-To-AD.ps1:287 char:28
+ UpdateUser @StudentParameters
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [UpdateUser], ParameterBindingException
+ FullyQualifiedErrorId : NamedParameterNotFound,UpdateUser
Just pass your hashtable in as usual
UpdateUser $StudentParamenters
Then you also want to change this inside your function
Function UpdateUser {
[CmdletBinding()]
param(
[PSObject[]]$PassedUser
)
try {
# -Replace is expecting a hashtable so provide it as-is, do not splat (@) here either
Get-ADUser -Identity $($PassedUser.studentID) | Set-ADUser -Replace $PassedUser
}
catch {
Write-Error "Error updating user: $($PassedUser.lname), $($PassedUser.fname)"
}
} # END UpdateUser
Update
Updated UpdateUser function and put it all together including changes recommended by mclayton. I coded it a little differently because I had trouble using it exactly as mclayton suggested (could not modify the hashtable while it was being enumerated) (fixed. Thank you mclayton)
<#
Name: UpdateUser
Params: [PSObject[]]User
Instructions: Call function and pass in a User object
which has all values needed to update Set-ADUser.
#>
Function UpdateUser {
[CmdletBinding()]
param(
[PSObject[]]$PassedUser
)
# Add mclaytons suggestions to not include empty/null values
# remove parameters that would break -Replace
foreach( $key in @($PassedUser.Keys) )
{
if( $null -eq $PassedUser[$key] )
{
$PassedUser.Remove($key)
}
}
try {
# Use $PassedUser hashtable which has been stripped of empty values
# KEEP -Replace parameter and pass in $PassedUser hashtable
Get-ADUser -Identity $($PassedUser.studentID) | Set-ADUser -Replace $PassedUser
}
catch {
Write-Error "Error updating user: $($PassedUser.lname), $($PassedUser.fname)"
}
} # END UpdateUser
<#
Check for existing user.
Either update an existing or create a new
#>
try {
Get-ADUser -Identity $user.studentID
# Account exists. Update name fields.
$StudentParameters = @{
ChangePasswordAtLogon = $False
Description = $($user.studentID)
DisplayName = ($user.lname + ', ' + $user.fname)
Mail = $($StudentEmail)
Enabled = $True
GivenName = $($user.fname)
Initials = $($MiddleInitial)
PasswordNeverExpires = $True
SamAccountName = $($user.studentID)
Surname = $($user.lname)
UserPrincipalName = ($user.studentID + '@' + $env:USERDNSDOMAIN.ToLower())
}
# DO NOT SPLAT StudentParameters!!
UpdateUser $StudentParameters
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityResolutionException] {
# Account does NOT exist. Create new user.
$StudentParameters = @{
ChangePasswordAtLogon = $False
Description = $($user.studentID)
DisplayName = ($user.lname + ', ' + $user.fname)
EmailAddress = $($StudentEmail)
Enabled = $True
GivenName = $($user.fname)
Initials = $($MiddleInitial)
Name = $($user.studentID)
PasswordNeverExpires = $True
Path = "OU=$user.actualGrade,OU=$user.schoolName,OU=Students,DC=" + $env:USERDNSDOMAIN.Substring(0, $env:USERDNSDOMAIN.Length - 4).ToLower() + ',DC=' + $env:USERDNSDOMAIN.Substring($env:USERDNSDOMAIN.Length - 3).ToLower()
SamAccountName = S($user.studentID)
Surname = $($user.lname)
UserPrincipalName = ($user.studentID + '@' + $env:USERDNSDOMAIN.ToLower())
}
CreateUser @StudentParameters
}
Upvotes: 2
Reputation: 10170
You might be hitting this issue with the -Replace
parameter from the Set-ADUser documentation:
-Replace
Specifies values for an object property that will replace the current values. Use this parameter to replace one or more values of a property that cannot be modified using a cmdlet parameter. To modify an object property, you must use the LDAP display name. You can specify multiple values to a property by specifying a comma-separated list of values, and more than one property by separating them using a semicolon.
If any of the properties have a null or empty value the cmdlet will return an error. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In which case the solution might be to not include the Initials
key in the $StudentParameters
hashtable if it's $null
or en empty string - e.g.:
$StudentParameters = @{
... etc ...
# Initials = $($MiddleInitial) # <---- remove this line
... etc ...
};
# set the property this way instead...
if( ($null -ne $MiddleInitial) -and ("" -ne $MiddleInitial) )
{
$StudentParameters.Add("Initials", $MiddleInitial);
}
This will probably also extend to all the other properties as well - e.g. GivenName = $($user.fname)
but maybe they're never $null
so you've not hit the problem on those properties before...
Update
If you want to extend the above pattern to all your properties, it might be cleaner to build the hashtable how you’re already doing it and then remove the null keys in a for loop before you call Set-ADUser
...
# remove parameters that would break -Replace
foreach( $key in @($StudentParameters.Keys) )
{
if( $null -eq $StudentParameters[$key] )
{
$StudentParameters.Remove($key)
}
}
Upvotes: 2
Reputation: 76
Since the hash table of params can be in any order, you can build the bulk of it as you have it and then deal with the problem params afterward.
$StudentParameters = @{
ChangePasswordAtLogon = $False
Description = $($user.studentID)
DisplayName = ($user.lname+", "+$user.fname)
Mail = $($StudentEmail)
Enabled = $True
GivenName = $($user.fname)
# Initials = $($MiddleInitial)
PasswordNeverExpires = $True
SamAccountName = $($user.studentID)
Surname = $($user.lname)
UserPrincipalName = ($user.studentID+"@"+$env:USERDNSDOMAIN.ToLower())
}
# mname property may be blank, so only add the parameter if nonblank
if ($user.mname) {
# append a line to the hash table
$StudentParameters += @{Initials = $user.mname}
}
UpdateUser @StudentParameters
If you prefer not to use the simple conditional test if ($user.mname)
then replace with whatever you are comfortable with. It works well for string variables in my experience.
Upvotes: 1