W3bGuy
W3bGuy

Reputation: 747

Powershell Active Directory - Update a users `Initials` property

*** 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

Answers (3)

Daniel
Daniel

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

mclayton
mclayton

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

jswanson
jswanson

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

Related Questions