Forum Discussion
Need help updating User Objects from CSV
- Jun 02, 2022
Here's a basic script that deals with the additional considerations mentioned above.
It does have some limits as noted within the script itself, but I'd wager you're not going to come across those anyway.
With respect to the attribute mappings between the CSV and Active Directory's attributes, the function named Get-CsvToADData is where you would make any necessary changes.
As an aside, it does not require the ActiveDirectory module, making it a bit more portable. This adds a handful of extra lines but the portability gain is easily worth the cost.
[cmdletbinding()] Param( [parameter(Mandatory=$true)][string]$Path ) <# .SYNOPSIS Updates an existing Active Directory object matching on employeeID. .DESCRIPTION Relatively basic script that features just enough error checking to not make a mess of Active Directory. It hasn't been written to cope with multi-valued attributes. #> #region Function definitions. # Define the CSV-to-AD attribute mappings. function Get-CsvToADData([System.Object] $User, [System.Collections.HashTable] $Replacements, [System.Collections.Generic.List[string]] $Clearances) { if ($Replacements -isnot [System.Collections.HashTable] -or $Clearances -isnot [System.Collections.Generic.List[string]]) { return($false); } #region Clear any previous values. $Replacements.Clear(); # Attributes to be updated. $Clearances.Clear(); # Attributes to be cleared. #endregion try { # The basic 1-to-1 mappings. if ([string]::IsNullOrWhiteSpace($User.last_name)) { $Clearances.Add("sn"); } else { $Replacements.Add("sn", $User.last_name); }; if ([string]::IsNullOrWhiteSpace($User.mi)) { $Clearances.Add("initials"); } else { $Replacements.Add("initials", $User.mi); }; if ([string]::IsNullOrWhiteSpace($User.first_name)) { $Clearances.Add("givenName"); } else { $Replacements.Add("givenName", $User.first_name); }; if ([string]::IsNullOrWhiteSpace($User.emp_title)) { $Clearances.Add("title"); } else { $Replacements.Add("title", $User.emp_title); }; if ([string]::IsNullOrWhiteSpace($User.phone)) { $Clearances.Add("telephoneNumber"); } else { $Replacements.Add("telephoneNumber", $User.phone); }; if ([string]::IsNullOrWhiteSpace($User.ext4)) { $Clearances.Add("ipPhone"); } else { $Replacements.Add("ipPhone", $User.ext4); }; if ([string]::IsNullOrWhiteSpace($User.fax_num)) { $Clearances.Add("facsimileTelephoneNumber"); } else { $Replacements.Add("facsimileTelephoneNumber", $User.fax_num); }; if ([string]::IsNullOrWhiteSpace($User.office_street)) { $Clearances.Add("streetAddress"); } else { $Replacements.Add("streetAddress", $User.office_street); }; if ([string]::IsNullOrWhiteSpace($User.office_city)) { $Clearances.Add("l"); } else { $Replacements.Add("l", $User.office_city); }; if ([string]::IsNullOrWhiteSpace($User.st)) { $Clearances.Add("st"); } else { $Replacements.Add("st", $User.st); }; if ([string]::IsNullOrWhiteSpace($User.off_zip)) { $Clearances.Add("postalCode"); } else { $Replacements.Add("postalCode", $User.off_zip); }; # Mappings involving multiple attributes and/or transformations. #region physicalDeliveryOfficeName $Has_office = -not [string]::IsNullOrWhiteSpace($User.office); $Has_locat = -not [string]::IsNullOrWhiteSpace($User.locat); if (-not ($Has_office -or $Has_locat)) { $Clearances.Add("physicalDeliveryOfficeName"); } elseif ($Has_office -and $Has_locat) { $Replacements.Add("physicalDeliveryOfficeName", "$($User.office) $($User.locat)"); } elseif ($Has_office) { $Replacements.Add("physicalDeliveryOfficeName", $User.office); } else { $Replacements.Add("physicalDeliveryOfficeName", $User.locat); }; #endregion } catch { $Replacements.Clear(); $Clearances.Clear(); throw; } return($Replacements.Count + $Clearances.Count -gt 0); } #endregion #region Script body. try { # Knock up an Active Directory searcher. #region Create an Active Directory searcher. $RootDSE = [adsi]"LDAP://RootDSE"; $CurrentServer = $RootDSE.Properties["dNSHostName"][0].ToLowerInvariant(); # Used here and there to ensure all read and write operations are using the same server during this run. $DefaultNamingContext = $RootDSE.Properties["defaultNamingContext"][0]; $RootDSE.Dispose(); $Searcher = [adsisearcher]::new([adsi]"LDAP://$CurrentServer/$DefaultNamingContext"); Write-Warning "Connected to domain controller $CurrentServer"; #endregion #region Create the lists that will hold the changes. $Replacements = [System.Collections.Hashtable]::new(); $Clearances = [System.Collections.Generic.List[string]]::new(); #endregion Import-Csv -Path $Path | ForEach-Object { $User = $_; # Check employeeID is not empty otherwise we're wasting our time. if (-not [string]::IsNullOrWhiteSpace($User.XREF_NUM)) { $EmployeeID = $User.XREF_NUM; #region Check if exactly one user can be found in Active Directory. $Searcher.Filter = "(&(objectClass=user)(objectCategory=person)(employeeID=$EmployeeID))"; $Results = $Searcher.FindAll(); #endregion #region If exactly one result was returned, proceed. if (($Results.Count -eq 1) -and (Get-CsvToADData -User $User -Replacements $Replacements -Clearances $Clearances)) { #region Fetch the Active Directory user and make any relevant changes. try { # Fetch the user from Active Directory. $ADUser = $Results[0].GetDirectoryEntry(); # Perform the relevant clearing and/or updating. foreach ($Attribute in $Replacements.Keys) { if ($ADUser.Properties.Contains($Attribute)) { # If the Active Directory attribute already has a value, we can simply change it. $ADUser.Properties[$Attribute].Value = $Replacements[$Attribute]; } else { # If the Active Directory attribute is null (not to be confused with an empty string), then we need to add a key-value pair. $null = $ADUser.Properties[$Attribute].Add($Replacements[$Attribute]); } } foreach ($Attribute in $Clearances) { # Clear the value from any attribute where no matching value was provided in the CSV. $ADUser.Properties[$Attribute].Clear() ; } $ADUser.CommitChanges(); } catch { # Electing to bail out of the script entirely by re-throwing the exception. Alternatively, you could elect to simply continue the loop but re-throwing is the lesser evil. throw; } finally { if ($ADUser -is [System.DirectoryServices.DirectoryEntry]) { $ADUser.Dispose(); } } #endregion } else { Write-Warning "Skipping EmployeeID ""$EmployeeID"". Results = $($Results.Count)."; $Results.Dispose(); } #endregion } } } catch { throw; } finally { if ($Searcher) { $Searcher.Dispose(); } } #endregion
Cheers,
Lain
Here's a basic script that deals with the additional considerations mentioned above.
It does have some limits as noted within the script itself, but I'd wager you're not going to come across those anyway.
With respect to the attribute mappings between the CSV and Active Directory's attributes, the function named Get-CsvToADData is where you would make any necessary changes.
As an aside, it does not require the ActiveDirectory module, making it a bit more portable. This adds a handful of extra lines but the portability gain is easily worth the cost.
[cmdletbinding()]
Param(
[parameter(Mandatory=$true)][string]$Path
)
<#
.SYNOPSIS
Updates an existing Active Directory object matching on employeeID.
.DESCRIPTION
Relatively basic script that features just enough error checking to not make a mess of Active Directory.
It hasn't been written to cope with multi-valued attributes.
#>
#region Function definitions.
# Define the CSV-to-AD attribute mappings.
function Get-CsvToADData([System.Object] $User, [System.Collections.HashTable] $Replacements, [System.Collections.Generic.List[string]] $Clearances)
{
if ($Replacements -isnot [System.Collections.HashTable] -or $Clearances -isnot [System.Collections.Generic.List[string]])
{
return($false);
}
#region Clear any previous values.
$Replacements.Clear(); # Attributes to be updated.
$Clearances.Clear(); # Attributes to be cleared.
#endregion
try
{
# The basic 1-to-1 mappings.
if ([string]::IsNullOrWhiteSpace($User.last_name)) { $Clearances.Add("sn"); } else { $Replacements.Add("sn", $User.last_name); };
if ([string]::IsNullOrWhiteSpace($User.mi)) { $Clearances.Add("initials"); } else { $Replacements.Add("initials", $User.mi); };
if ([string]::IsNullOrWhiteSpace($User.first_name)) { $Clearances.Add("givenName"); } else { $Replacements.Add("givenName", $User.first_name); };
if ([string]::IsNullOrWhiteSpace($User.emp_title)) { $Clearances.Add("title"); } else { $Replacements.Add("title", $User.emp_title); };
if ([string]::IsNullOrWhiteSpace($User.phone)) { $Clearances.Add("telephoneNumber"); } else { $Replacements.Add("telephoneNumber", $User.phone); };
if ([string]::IsNullOrWhiteSpace($User.ext4)) { $Clearances.Add("ipPhone"); } else { $Replacements.Add("ipPhone", $User.ext4); };
if ([string]::IsNullOrWhiteSpace($User.fax_num)) { $Clearances.Add("facsimileTelephoneNumber"); } else { $Replacements.Add("facsimileTelephoneNumber", $User.fax_num); };
if ([string]::IsNullOrWhiteSpace($User.office_street)) { $Clearances.Add("streetAddress"); } else { $Replacements.Add("streetAddress", $User.office_street); };
if ([string]::IsNullOrWhiteSpace($User.office_city)) { $Clearances.Add("l"); } else { $Replacements.Add("l", $User.office_city); };
if ([string]::IsNullOrWhiteSpace($User.st)) { $Clearances.Add("st"); } else { $Replacements.Add("st", $User.st); };
if ([string]::IsNullOrWhiteSpace($User.off_zip)) { $Clearances.Add("postalCode"); } else { $Replacements.Add("postalCode", $User.off_zip); };
# Mappings involving multiple attributes and/or transformations.
#region physicalDeliveryOfficeName
$Has_office = -not [string]::IsNullOrWhiteSpace($User.office);
$Has_locat = -not [string]::IsNullOrWhiteSpace($User.locat);
if (-not ($Has_office -or $Has_locat))
{
$Clearances.Add("physicalDeliveryOfficeName");
}
elseif ($Has_office -and $Has_locat)
{
$Replacements.Add("physicalDeliveryOfficeName", "$($User.office) $($User.locat)");
}
elseif ($Has_office)
{
$Replacements.Add("physicalDeliveryOfficeName", $User.office);
}
else
{
$Replacements.Add("physicalDeliveryOfficeName", $User.locat);
};
#endregion
}
catch
{
$Replacements.Clear();
$Clearances.Clear();
throw;
}
return($Replacements.Count + $Clearances.Count -gt 0);
}
#endregion
#region Script body.
try
{
# Knock up an Active Directory searcher.
#region Create an Active Directory searcher.
$RootDSE = [adsi]"LDAP://RootDSE";
$CurrentServer = $RootDSE.Properties["dNSHostName"][0].ToLowerInvariant(); # Used here and there to ensure all read and write operations are using the same server during this run.
$DefaultNamingContext = $RootDSE.Properties["defaultNamingContext"][0];
$RootDSE.Dispose();
$Searcher = [adsisearcher]::new([adsi]"LDAP://$CurrentServer/$DefaultNamingContext");
Write-Warning "Connected to domain controller $CurrentServer";
#endregion
#region Create the lists that will hold the changes.
$Replacements = [System.Collections.Hashtable]::new();
$Clearances = [System.Collections.Generic.List[string]]::new();
#endregion
Import-Csv -Path $Path | ForEach-Object {
$User = $_;
# Check employeeID is not empty otherwise we're wasting our time.
if (-not [string]::IsNullOrWhiteSpace($User.XREF_NUM))
{
$EmployeeID = $User.XREF_NUM;
#region Check if exactly one user can be found in Active Directory.
$Searcher.Filter = "(&(objectClass=user)(objectCategory=person)(employeeID=$EmployeeID))";
$Results = $Searcher.FindAll();
#endregion
#region If exactly one result was returned, proceed.
if (($Results.Count -eq 1) -and (Get-CsvToADData -User $User -Replacements $Replacements -Clearances $Clearances))
{
#region Fetch the Active Directory user and make any relevant changes.
try
{
# Fetch the user from Active Directory.
$ADUser = $Results[0].GetDirectoryEntry();
# Perform the relevant clearing and/or updating.
foreach ($Attribute in $Replacements.Keys)
{
if ($ADUser.Properties.Contains($Attribute))
{
# If the Active Directory attribute already has a value, we can simply change it.
$ADUser.Properties[$Attribute].Value = $Replacements[$Attribute];
}
else
{
# If the Active Directory attribute is null (not to be confused with an empty string), then we need to add a key-value pair.
$null = $ADUser.Properties[$Attribute].Add($Replacements[$Attribute]);
}
}
foreach ($Attribute in $Clearances)
{
# Clear the value from any attribute where no matching value was provided in the CSV.
$ADUser.Properties[$Attribute].Clear() ;
}
$ADUser.CommitChanges();
}
catch
{
# Electing to bail out of the script entirely by re-throwing the exception. Alternatively, you could elect to simply continue the loop but re-throwing is the lesser evil.
throw;
}
finally
{
if ($ADUser -is [System.DirectoryServices.DirectoryEntry])
{
$ADUser.Dispose();
}
}
#endregion
}
else
{
Write-Warning "Skipping EmployeeID ""$EmployeeID"". Results = $($Results.Count).";
$Results.Dispose();
}
#endregion
}
}
}
catch
{
throw;
}
finally
{
if ($Searcher)
{
$Searcher.Dispose();
}
}
#endregion
Cheers,
Lain
- Baron164Jun 13, 2022Brass Contributor
LainRobertson
I've been asked to also assign a user's Manager attribute via this process. I was able to get them to include the Manager's EmployeeID number as a new column. So while the Employee's ID number is listed in "XREF_NUM" column the user's Manager's ID number is in the new column "SUPV".
So now the issue is, how to lookup the Manager's User Account using their EmployeeID Attribute, pull the DistinguishedName Attribute from the Manager's User Account, and assign that value to the Manager attribute of the user account. - Baron164Jun 02, 2022Brass Contributor
LainRobertson
Thanks, I've tested the shorter version and so far it looks to be working properly. I'll look over the longer version and play with that. Due to the nature of the CSV data NULL values and duplicate EmployeeID's "should not" be an issue. But it's always possible that someone will fat finger a number.