0

I am processing an array of AD User data pulled from one domain to recreate in another. I have created a hash table linking the New-ADUser parameters with the user data imported from a CSV (populated from the domain I intend to recreate). When I call New-ADUser with the hash table, the user is not created and there are no error.

Here is the hash table:

$NewUserAttr = @{
    'Name'              = $ADUser.UsersName
    'SamAccountName'    = $ADUser.UsersSamAccountName
    'Company'           = $ADUser.UsersCompany
    'Department'        = $ADUser.UsersDepartment
    'DisplayName'       = $ADUser.UsersDisplayName
    'EmailAddress'      = $ADUser.UsersMail
    'EmployeeID'        = $ADUser.UsersEmployeeID
    'Enabled'           = $UsersEnabled
    'GivenName'         = $ADUser.UsersGivenName
    'Initials'          = $ADUser.UsersInitials
    'Manager'           = $ADUser.Manager
    'MobilePhone'       = $ADUser.UsersMobileNum
    'OfficePhone'       = $ADUser.UsersTelephoneNumber
    'PostalCode'        = $ADUser.UsersPostalCode
    'State'             = $ADUser.UsersST
    'StreetAddress'     = $ADUser.UsersStreetAddress
    'Surname'           = $ADUser.UsersSN
    'Title'             = $ADUser.UsersTitle
    'userPrincipalname' = $ADUser.UsersUPN
    'Path'              = $ParentOU
    'Server'            = $TargetDomain

    'OtherAttr' = @{
        'c'                  = $ADUser.Usersc
        'GIDNumber'          = $ADUser.UsersGIDNumber
        'l'                  = $ADUser.UsersL
        'LoginShell'         = $ADUser.UsersLoginShell
        'msSFU30Name'        = $ADUser.UsersMsSFU30Name
        'msSFU30NisDomain'   = $ADUser.UsersMsSFU30NisDomain
        'PhysicalDeliveryOfficeName' = $ADUser.UsersPhysicalDeliveryOfficeName
        'SSN'                = $ADUser.UsersSSN
        'Uid'                = $ADUser.UsersUid
        'uidNumber'          = $ADUser.UsersUidNum
        'unixHomeDirectory'  = $ADUser.UsersUHD
    }
}

PS > New-ADUser @NewUserAttr

I have reduced the NewUserAttr to Name, SamAccountName, Path, and Server and that did create the user, but that is far less parameters than what I need.

GeoffS
  • 3
  • 3
  • 3
    what error do you get? ///// what exact version of PoSh are you running? – Lee_Dailey Apr 05 '22 at 01:03
  • Could be some problems with data not being valid in the new domain, like the manager or the UPN. Can you strip the properties down to only string / basic values, see how that goes? – Guy S Apr 05 '22 at 01:10
  • At first glance, the properties you're using for your splatting seem correct, you just need to validate that the data being fed from your CSV is correct and the property values of your hashtable are right. You could also try `-Verbose` to see if that gives you a hint – Santiago Squarzon Apr 05 '22 at 01:26
  • Version 7.1.5. @GuyS I am currently only testing against a single user for simplicity. I will say that some of these values will be null for some users, but not for all. I may have to just start adding attribute at a time, which I was hoping to avoid. I know it creates the user with just Name, SamAcct, Path, and Server. I will try verbose as well. – GeoffS Apr 05 '22 at 12:12
  • Some more background. I am updating one of our scripts that was using the following to accomplish recreating the users: `New-ADUser -name $UsersName -samaccountname UsersSamAccountName -Path $ParentOU -Server $TargetDomain; if ($UsersEmployeeID) { set-aduser -identity $UsersSamAccountName -EmployeeID $UsersEmployeeID -Server $TargetDomain }` And so on with set-aduser for each attribute, looping through 19k+ users. Splatting has to be more efficient! @SantiagoSquarzon, Verbose wasn't much help. "VERBOSE: Performing the operation "New" on target " – GeoffS Apr 05 '22 at 12:23
  • Some of the attributes under `OtherAttributes` (not `OtherAttr`) could be set as normal properties: `l` --> `City`, `c` --> `Country`, `physicalDeliveryOfficeName` --> `Office`. Others I don't recognize, must be custom attributes in your schema. Are you sure these custom attributes are also defined in the new domain? – Theo Apr 05 '22 at 12:29
  • @Theo I am honestly not sure if they are defined. I didn't build the new domain and the person who did doesn't work with us anymore (I'm his replacement). I have tried running New-ADUser without the OtherAttributes and am still getting the same results. I will continue testing! – GeoffS Apr 05 '22 at 12:57
  • On mobile now, but I can give some code to deal with empty fields in the csv if you like. In the meantime, put the New-AdUser line inside a `try{..} catch{..}` and append `-ErrorAction Stop` to it. Then in the catch you can examine the error in `$_.Exception.Message` to see where it failed – Theo Apr 05 '22 at 13:59
  • @Theo, not sure why I didn't think of capturing potential errors using try/catch. I do use it in the actual script! So apparently it does not like null parameter values. `Cannot validate argument on parameter 'OtherAttributes'. The argument is null or an element of the argument collection contains a null value.` – GeoffS Apr 05 '22 at 14:25
  • Ok, so after commenting out the null attr and adding the 'PasswordNotRequired = $true' attribute (will set using Set-ADAccountPassword), the user is created. So unless @Theo has some better logic for handling the potential null attributes, I am likely going to use the suggestion from this thread by Richard Mueller: [link](https://social.technet.microsoft.com/Forums/scriptcenter/en-US/963bbb76-c1ee-46ce-83dd-6bfa02ebedc7/newaduser-adding-otherattributes-error-?forum=winserverpowershell). I am open to other suggestions as well. If there are none, I will add this comment to the Answer section. – GeoffS Apr 05 '22 at 15:01

3 Answers3

0

Continuing from my comments:

To avoid empty fields from being assembled in the attributes Hashtable to use for splatting, you could create two lookup tables in which you  map the CSV header names with the actual AD user attribute names.
Something like this:

    # create two mapping lookup Hashtables for 'normal' attributes and one for the OtherAttributes
    # format is: PropertyNameFromCsv = PropertyNameForActiveDirectory
    $attribMap = @{
        UsersName           = 'Name'
        UsersSamAccountName = 'SamAccountName'
        UsersCompany        = 'Company'
        Usersc              = 'Country'
        Manager             = 'Manager'
        # etc.
    }

    $otherMap = @{
        UsersGIDNumber        = 'GIDNumber'
        UsersLoginShell       = 'LoginShell'
        UsersmsSFU30Name      = 'MsSFU30Name'
        UsersmsSFU30NisDomain = 'MsSFU30NisDomain'
        # etc.
    }

Next, import the CSV and loop over each entry:

$csv = Import-Csv -Path 'X:\your_importFile.csv'

foreach ($item in $csv) {
    # two empty Hashtables for splatting
    $NewUserAttr = @{}
    $OtherAttr   = @{}
    # pre fill the default attributes you need for all users
    $NewUserAttr['Enabled'] = $UsersEnabled
    $NewUserAttr['Server']  = $TargetDomain
    $NewUserAttr['Path']    = $ParentOU

    # loop over the properties for each item in the CSV and only store 
    # the ones that are not empty and for which you can find a mapping
    $item.PsObject.Properties | ForEach-Object {
        if (![string]::IsNullOrWhiteSpace($_.Value)) { 
            if ($attribMap.Contains($_.Name))    { $NewUserAttr[$attribMap[$_.Name]] = $_.Value }
            elseif ($otherMap.Contains($_.Name)) { $OtherAttr[$otherMap[$_.Name]] = $_.Value }
        }
    }
    # join the hashtables together if we have OtherAttributes
    if ($OtherAttr.Count) { $NewUserAttr['OtherAttributes'] = $OtherAttr }

    # now try and create the new user
    try {
        New-ADUser @NewUserAttr -ErrorAction Stop
    }
    catch {
        Write-Warning "Error creating user $($NewUserAttr.Name): $($_.Exception.Message)"
    }
}
Theo
  • 57,719
  • 8
  • 24
  • 41
  • This works great. Getting ready to run some speed comparisons. In the joining of hashtables, I believe you meant "if ($OtherAttr.Count)" instead of "if($otherMap.count)". – GeoffS Apr 06 '22 at 14:34
  • @GeoffS Thanks! I didn't notice that typo earlier, but you're right, should be `$OtherAttr`. I have edited the answer. Please let us know which method comes out fastest (although speed wasn't a requirement in the question) – Theo Apr 06 '22 at 14:36
  • 1
    you are absolutely right, speed wasn't a requirement, just an after thought on my part. You'll be happy to know that your logic was 10 seconds faster! I also prefer it to mine as I think it just looks cleaner and can handle more null values. Thanks again! Yours took 58min, 53secs Mine took 59min, 3secs – GeoffS Apr 06 '22 at 20:55
0

I run into this often with named parameters and null values. To avoid null values for your named parameters which $null indicates the parameter should be omitted rather than provided, follow these rules when creating the splat-hashtable:

  1. When defining the hashtable, define any properties that will not (or should not be) $null, like so:

    $splatArgs = @{
      ParameterOne = 'Value'
      ParameterTwo = 'OtherValue'
    }
    
  2. For parameters that may or may not need to be provided based on whether its value is $null (or evaluates falsey), conditionally add it to the hashtable:

    if( $someVar ) {
      $splatArgs.ConditionalParameter = $someVar
    }
    

Repeat 2. for each conditional argument you have. Alternatively, you could initialize the hashtable with all possible parameter names and values, then strip them out after after checking falsiness, comparing to $null, etc.

  # You could do any condition here but in this example,
  # We will do a simple falsiness test on each key's value
  $removeParameterNames = $splatArgs.Keys | Where-Object {
    !( $splatArgs.$_ )
  }

  # Use another loop here since we don't want to modify the hashtable
  # while iterating over its keys
  foreach( $key in $removeParameterNames ) {
    $splatArgs.Remove($key)
  }
codewario
  • 19,553
  • 20
  • 90
  • 159
0

Here is what I ended up getting to work, which is very similar to what Bender recommended and what Richard from the link in my above comments recommended.

$NewUserAttr = @{
    'Name'                  = $ADUser.UsersName
    'SamAccountName'        = $ADUser.UsersSamAccountName
    'AccountPassword'       = (ConvertTo-SecureString -AsPlainText "<Ab@1Cd!2>" -Force)
    'Company'               = $ADUser.UsersCompany
    'Department'            = $ADUser.UsersDepartment
    'DisplayName'           = $ADUser.UsersDisplayName
    'EmailAddress'          = $ADUser.UsersMail 
    'EmployeeID'            = $ADUser.UsersEmployeeID
    'Enabled'               = $UsersEnabled
    'GivenName'             = $ADUser.UsersGivenName
    'MobilePhone'           = $ADUser.UsersMobileNum 
    'OfficePhone'           = $ADUser.UsersTelephoneNumber 
    'SurName'               = $ADUser.UsersSN 
    'Title'                 = $ADUser.UsersTitle
    'userPrincipalname'     = $ADUser.UsersUPN
    'Path'                  = $ParentOU
    'Server'                = $TargetDomain

    'OtherAttr' = @{
        'GIDNumber'                     = $ADUser.UsersGIDNumber
        'LoginShell'                    = $ADUser.UsersLoginShell
        'msSFU30Name'                   = $ADUser.UsersMsSFU30Name
        'msSFU30NisDomain'              = $ADUser.UsersMsSFU30NisDomain
        'Uid'                           = $ADUser.UsersUid
        'uidNumber'                     = $ADUser.UsersUidNum
        'unixHomeDirectory'             = $ADUser.UsersUHD
    } 
}

# Check the uncommon attributes and add them to the hash table if not null
if($ADUser.Usersl){$NewUserAttr.add('City',$ADUser.Usersl)}
if($ADUser.Usersc){$NewUserAttr.add('Country',$ADUser.Usersc)}
if($ADUser.UsersInitials){$NewUserAttr.add('Initials',$ADUser.UsersInitials)}
if($ADUser.Manager){$NewUserAttr.add('Manager',$ADUser.Manager)}
if($ADUser.UsersPostalCode){$NewUserAttr.add('PostalCode',$ADUser.UsersPostalCode)}
if($ADUser.UsersST){$NewUserAttr.add('State',$ADUser.UsersST)}
if($ADUser.UsersStreetAddress){$NewUserAttr.add('StreetAddress',$ADUser.UsersStreetAddress)}
if($ADUser.physicaldeliveryofficename){$NewUserAttr.OtherAttr.add('physicaldeliveryofficename',$ADUser.physicaldeliveryofficename)}
if($ADUser.UsersSSN){$NewUserAttr.OtherAttr.add('SSN',$ADUser.UsersSSN)}

#Add new user to destination domain
try {
    $UserExists = Get-ADUser -Identity $ADUser.UsersName -Server $TargetDomain
    if ($UserExists) {
        "Exists,$($ADUser.UsersName),$ParentOU`n" | Out-File $UserCreationLog -Append -Force
    } else {
        New-ADUser @NewUserAttr -ErrorAction Continue
        #Change password
        "Added,$($ADUser.UsersSamAccountName),$($ADUser.UsersName),$ParentOU`n" | Out-File $UserCreationLog -Append -Force
    }
} catch {
    $ErrorMsg = $_.Exception.Message
    Write-Log -Message "Unable to create user, $($ADUser.UsersName): $ErrorMsg." -Severity Error -LogPath $LogFile
    Write-Log -Message "Failed users attributes: $NewUserAttr $($NewUserAttr.OtherAttr)" -Severity Error -LogPath $LogPath
}

Now I just need to test each of these suggest answers to see which is the fastest! Thanks everyone!!

GeoffS
  • 3
  • 3