Manipulating multiproperty AD attributes (ProxyAddresses) - powershell

I have a list of users with several values in their ProxyAddresses attribute e.g.
SMTP:JohnSmith#domain1.com
smtp:jsmith#domain2.com
smtp:ukCC10s#domain2.com
smtp:smith.john#domain3.com
and many other unknown ones.
What I want to do is:
Convert all existing addresses that begin with smtp/SMTP to lowercase
Add/replace the one that conforms to the SMTP:firstname.surname#Domain2.com standard (to make it the primary)
I've not got very far, running this just strips all proxyaddresses out and adds the one specified:
$userou = "OU=test2,OU=Test,OU=Users,DC=Corp,DC=Local"
$users = Get-ADUser -Filter * -SearchBase $userou -Properties SamAccountName, ProxyAddresses
foreach ($user in $users) {
Get-ADUser $user | Set-ADUser -Replace #{'ProxyAddresses'="SMTP:blah#blah.com"}
}
How do I enumerate through each value in a multivalue attribute?

The below code should do what you need.
It updates the ProxyAddresses multivalue property so that all 'smtp/SMTP' addresses will become lowercase and the new Primary emailaddress is computed and inserted in the list.
I have added a small helper function to replace diacritic characters that may appear in the users first or last name because especially Outlook 365 does not handle these characters very well.
$userou = "OU=test2,OU=Test,OU=Users,DC=Corp,DC=Local"
$users = Get-ADUser -Filter * -SearchBase $userou -Properties SamAccountName, ProxyAddresses, EmailAddress
function Replace-Diacritics {
# helper function to replace characters in email addresses that especially Outlook365 does not like..
Param(
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[string] $EmailAdress
)
$chars = #()
$normalized = $EmailAdress.Normalize( [Text.NormalizationForm]::FormD )
$normalized.ToCharArray() | ForEach-Object {
if( [Globalization.CharUnicodeInfo]::GetUnicodeCategory($_) -ne [Globalization.UnicodeCategory]::NonSpacingMark) {
$chars += $_
}
}
$chars -join ''
}
foreach ($user in $users) {
# create the new primary emailaddress
# remove invalid characters and replace diacritics ("Frédérique.Étrangér#Domain2.com" --> "frederique.etranger#domain2.com")
$newPrimary = ($("{0}.{1}#Domain2.com" -f $user.GivenName, $user.Surname) -replace '[\s()<>,;:''"{}/[\]\\]+', '').ToLower()
$newPrimary = "SMTP:" + (Replace-Diacritics ($newPrimary -replace '\.+', '.'))
# get all email addresses and convert them to lowercase. At the same time dedupe this array.
# this will also replace 'SMTP:' of the former Primary email address to become an alias ('smtp:')
$emailAliases = #($user.ProxyAddresses | Where-Object { $_ -match '^smtp:.*' -and $_ -ne $newPrimary } |
ForEach-Object { $_.ToLower() } |
Sort-Object -Unique)
# read all other existing stuff
$otherAddresses = #($user.ProxyAddresses | Where-Object { $_ -notmatch '^smtp:.*' })
# now merge all addresses into one array, the Primary email address on top for easier reading in ADUC
$newProxies = (#($newPrimary) + $emailAliases) + $otherAddresses
# finally replace the users ProxyAddresses property. I like:
$user | Set-ADUser -Clear ProxyAddresses
$user | Set-ADUser -Add #{'proxyAddresses'=$newProxies }
# but you could also do
# $user | Set-ADUser -Replace #{'proxyAddresses' = $newProxies}
# finally, put the new primary email address in the users 'mail' property
$user | Set-ADUser -EmailAddress $($newPrimary -replace 'SMTP:', '')
}

Untested, because I don't have an AD at my disposal here, but I'd expect something like this to work, since multivalued attributes should be returned as collections.
$addr = $user.ProxyAddresses -creplace '^SMTP:', 'smtp:'
$addr += 'SMTP:blah#blah.com'
$user | Set-ADUser -Replace #{ 'ProxyAddresses' = $addr }
To assign the correct new primary address to each user you could map the addresses to usernames in a hashtable and then do a lookup rather than assign the new primary address as a static value:
$addr += $newPrimaryAddress[$user.SamAccountName]

Related

Using PowerShell to add the proxyAddresses attribute with specific format?

I need to add new email aliases to the users in the specific OU, with a specific format like:
user Alias / sAMAccountName = First Lastname
newAlias = FLastname#NewBrandX.com
User with Apostrophe
user Alias / sAMAccountName = John O'Dea
newAlias = JOdea#NewBrandX.com
User with either First or Lastname with spaces
user Alias / sAMAccountName = Peter Van Denberg
newAlias = PVanDenberg#NewBrandX.com
However, my script skill is quite limited, so I wonder if anyone can assist in the script modification below:
$newproxy = "#NewBrandX.com"
$userou = 'OU=Users,OU=Seattle,DC=usa,DC=contoso,DC=com'
$paramGetADUser = #{
Filter = 'Enable -eq $true'
SearchBase = $userou
Properties = 'SamAccountName', 'ProxyAddresses', 'givenName', 'Surname'
}
$users = Get-ADUser #paramGetADUser
Foreach ($user in $users)
{
$FirstName = $user.givenName.ToLower() -replace '\s', ''
$LastName = $user.surname.ToLower() -replace '\s', '' -replace "'", ''
Set-ADUser -Identity $user.samaccountname -Add #{ proxyAddresses = "smtp:" + $FirstName + $LastName + $newproxy }
}
It looks to me like you want a create a new proxy address in format
First character of GivenName
Surname without apostrophes or spaces
followed by "#NewBrandX.com".
Your code however takes the full GivenName.
To add to the ProxyAddresses array, you need to replace the entire [string[]] array
$newproxy = "#NewBrandX.com"
$userou = 'OU=Users,OU=Seattle,DC=usa,DC=contoso,DC=com'
$paramGetADUser = #{
Filter = 'Enable -eq $true'
SearchBase = $userou
Properties = 'ProxyAddresses' # SamAccountName, GivenName and Surname are returned by default
}
$users = Get-ADUser #paramGetADUser
foreach ($user in $users) {
$newAddress = ("smtp:{0}{1}$newproxy" -f $user.GivenName.Trim()[0], ($user.Surname -replace "[\s']+").ToLower())
$proxies = #($user.ProxyAddresses)
# test if this user already has this address in its ProxyAddresses attribute
if ($proxies -contains $newAddress) {
Write-Host "User $($user.Name) already has '$newAddress' as proxy address"
}
else {
Write-Host "Setting new proxy address '$newAddress' to user $($user.Name)"
$proxies += $newAddress
# proxyAddresses needs a **strongly typed** string array, that is why we cast the $proxies array with [string[]]
$user | Set-ADUser -Replace #{ proxyAddresses = [string[]]$proxies }
}
}

PowerShell Get-ADUser - Using custom AD attibutes as IF condition

What I need to do is to export specific AD users and some of their properties to a CSV file. What I need to have there is some of the default properties like Name, SamAccountName, Enabled and some custom ones: businesscategory, extensionAttribute9 etc.
I'm struggling with my if - else statements, as they seem to not be comparing employeenumber to $null
$name = Read-Host -Prompt "Please enter the name for output file."
$filename = $name+".csv"
$domain = #('DOMAIN1','DOMAIN2','DOMAIN3','DOMAIN4')
$result = foreach ($item in $domain) {
Get-ADUser -server $item -Properties businesscategory, extensionAttribute4,
extensionAttribute9, extensionAttribute13, employeenumber, Enabled -ResultPageSize 100 -Filter *
if (($null -ne $_.employeenumber) -and ($_.employeenumber -notlike '*svc*')) {
Select-Object Name,
SamAccountName,
UserPrincipalName,
#{n="businesscategory"; e={$_.businesscategory -join ", "}},
#{n="extensionAttribute4";e={$_.extensionAttribute4 -join ", "}},
#{n="extensionAttribute9";e={$_.extensionAttribute9 -join ", "}},
#{n="extensionAttribute13";e={$_.extensionAttribute13 -join ", "}},
DistinguishedName, employeenumber, Enabled
} else { (...)
The above is part of my code where it should enter into first if. It does that, but it exports all accounts, whether employeenumber is present or not.
Another issue is that the exported CSV doesn't contain columns created from custom attributes, instead it shows some other properties that I did not ask for.
This used to work fine if I used Where-Object instead of if - else and checked the values like below:
Where-Object {
($_.SamAccountName -notlike '*proprietary*') -and
($_.UserPrincipalName -notlike '*proprietary*') -and
($_.SamAccountName -notlike '*mailbox*') -and (...)
Unfortunately I need to use if - else to make more complex comparisons and selections, but can't figure it out
The problem is in this line:
$result = foreach ($item in $domain) {
Get-ADUser -server $item -Properties ... # => not assigned to any variable
Then in this line:
if (($null -ne $_.employeenumber) -and ($_.employeenumber -notlike '*svc*')) {
Since $_ doesn't exist, you are comparing something like:
$null -ne $null -and $null -notlike '*svc*'
Which will always be $false. It's also worth mentioning that this is a foreach loop, different from ForEach-Object, the automatic variable $_ ($PSItem) doesn't mean anything here.
The next problem comes when using Select-Object as the beginning of the statement, there is no object being piped to it.
Select-Object Name, SamAccountName, UserPrincipalName, ...
In this case, the if condition could be removed completely with some LDAP Filtering:
# employee number is not `$null` AND employee number is not like `*svc*`
-LDAPFilter "(&(employeenumber=*)(!employeenumber=*svc*))"
The code would look like this:
$name = Read-Host -Prompt "Please enter the name for output file."
$filename = $name + ".csv" # Consider using `$HOME` here, or an absolute Path
$param = #{
LDAPFilter = "(&(employeenumber=*)(!employeenumber=*svc*))"
ResultPageSize = 100
Properties = #(
'businesscategory'
'extensionAttribute4'
'extensionAttribute9'
'extensionAttribute13'
'employeenumber'
)
}
'DOMAIN1','DOMAIN2','DOMAIN3','DOMAIN4' | ForEach-Object {
$param['Server'] = $_
foreach($user in Get-ADUser #param) {
[pscustomobject]#{
Name = $user.Name
SamAccountName = $user.SamAccountName
UserPrincipalName = $user.UserPrincipalName
BusinessCategory = $user.businesscategory -join ", "
extensionAttribute4 = $user.extensionAttribute4 -join ", "
extensionAttribute9 = $user.extensionAttribute9 -join ", "
extensionAttribute13 = $user.extensionAttribute13 -join ", "
DistinguishedName = $user.DistinguishedName
employeenumber = $user.employeenumber
Enabled = $user.Enabled
Domain = $_ # Adding the Domain of this user here
}
}
} | Export-Csv $filename -NoTypeInformation

Exporting certain users' samAccountName from AD to csv with only EmployeeNumbers known

I am trying to export certain users' SamAccountName and EmployeeNumber from AD to csv. I only have EmployeeNumbers from HR in csv file and have to match it to SamAccountName in AD. My code does not work past 'if' condition. When I echo output there are same values for $a and $b all in String type. This file C:\temp\UsersToDisable.csv contains 4 and 3 number with column name "EmployeeNumber". This is what i came up with:
Import-Module ActiveDirectory
$Nums = Import-Csv "C:\powershell\EmployeeNumbers.csv"
$Users = Get-ADUser -Filter "*" -Property EmployeeNumber -SearchBase
"DC=my,DC=example,DC=com" | Where { $_.EmployeeNumber -ne $null } |
Select SamAccountName,EmployeeNumber
Foreach ($user in $Users)
{
$EmployeeNumber = $user.EmployeeNumber
foreach ($Line in $Nums)
{
$number = $line.EmployeeNumber
$a = $number.toString()
$b = $EmployeeNumber.toString()
echo $a $b
if($a -eq $b)
{
echo $user.SamAccountName
$result += ,(Get-ADUser $user.SamAccountName -Properties * | Select SamAccountName,employeeNumber)
}
}
} $result | Export-CSV "C:\temp\CCI_All_Users.csv" -NoTypeInformation
Thank you for any advice!
Loading all the users from the directory and then searching once again for the same user in the directory is not an ideal solution for performance reason.
Try the following code. For each number from the input file, search in the directory the user having the current EmployeeNumber, requesting to load the EmployeeNumber property. Then select only the desired properties and export them to CSV.
Import-Module ActiveDirectory
$Nums = Import-Csv "C:\powershell\EmployeeNumbers.csv"
$Nums | ForEach-Object {
Get-ADUser -LdapFilter "(EmployeeNumber=$_)" -Property EmployeeNumber -SearchBase "DC=my,DC=example,DC=com" |
Select-Object SamAccountName, EmployeeNumber
} | Export-CSV "C:\temp\CCI_All_Users.csv"
An alternative to Hazrelle's helpful answer, very similar but instead of looping over each line of the CSV, taking advantage of the LDAPFilter capabilities with the help of some string manipulation:
# This assumes there is a column named "EmployeeNumber" in the CSV
$Nums = (Import-Csv "C:\powershell\EmployeeNumbers.csv").EmployeeNumber
# Assuming Nums is an Array with Employee Numbers. i.e.:
# $Nums => A123,A456,A789
# $filter would look like this:
# (|(employeenumber=A123)(employeenumber=A456)(employeenumber=A789))
$filter = [string]::Format(
'(|(employeenumber={0}))',
($Nums -join ')(employeenumber=')
)
$params = #{
Properties = 'EmployeeNumber'
SearchBase = 'DC=my,DC=example,DC=com'
LDAPFilter = $filter
}
Get-ADUser #params | Select-Object samAccountName, EmployeeNumber |
Export-CSV "C:\temp\CCI_All_Users.csv" -NoTypeInformation
Worth mentioning, $result += ,(Get... is not recommended.

Powershell ForEach replace (Bulk change primary SMTP in AD)

I need switch the primary SMTP address in AD in bulk from users of an certain OU.
The challenge;
User1
smtp:first.last#domain1.com
smtp:flast#domain1.com
SMTP:first.last#domain2.net
smtp:flast#domain2.net
I need to make the first.last#domain1 the primary SMTP.
So far I have come to this;
$proxies = $null
Get-ADUser -Filter * -SearchBase "OU=users_test,OU=Test,DC=test,DC=local" -Properties name,mail,ProxyAddresses |
Foreach {
$proxies = $_.ProxyAddresses |
ForEach-Object{
$a = $_ -replace 'SMTP','smtp'
if($a -match 'domain1.com'){
$a -replace 'smtp','SMTP'
Write-Host $a
}else{
$a
}
}
$_.ProxyAddresses = $proxies
#Set-ADUser -instance $_
Write-host $proxies
}
The problem:
When I run the above script it obviously make both aliases with domain1.com the primary by replacing the smtp with SMTP on all that it finds matching the domain1.com.
Question: How can I make so it replaces only one?
I hope I explain myself good enough. Thank you in advance for any coming help 🙏
Since it's not necessary to pick one particular domain1.com address, try this.
I've added a flag variable to set the primary address only once per user.
Furthermore I switched to the -like operator, as the -match operator isn't necessary and just produces more overhead if not used correctly.
And I've added the "start of string" regex character to your replacement parts (-replace also uses regex pattern)
Get-ADUser -Filter * -SearchBase 'OU=users_test,OU=Test,DC=test,DC=local' -Properties name, mail, ProxyAddresses |
ForEach-Object {
# flag to avoid further processing after first match
$userDone = $false
$proxies = $_.ProxyAddresses |
ForEach-Object {
$proxyAddress = $_ -replace '^SMTP', 'smtp'
if (!$userDone -and $proxyAddress -like '*#domain1.com') {
$proxyAddress -replace '^smtp', 'SMTP'
$userDone = $true
} else {
$proxyAddress
}
}
$_.ProxyAddresses = $proxies
#Set-ADUser -instance $_
Write-Host $proxies
}
Update 2021-01-13
Here's an update according to your request in the comments below.
Could you show me how I could use the same script that would choose first.last#domain1.com. The ForEach should change to primary the one that has first.last.
Now regex makes more sense ;)
The code is untested against Active Directory, but should work.
The regex pattern in short:
(?i) >case-insensitive match (=regex option)
^ >start of string
(?: >non-capturing group (capturing is not required in your case)
smtp: >starts with 'smtp:'
[^\.\#]+ >matches any char at least once excluding '.' and '#'
\. >matches '.' once
[^\.\#]+ >matches any char at least once excluding '.' and '#'
#domain1\.com >matches '#domain1.com'
)
$ >end of string
For more details please look at: https://regex101.com/r/atKdSw/1/
I've additionally added a warning when no match has been made due to whatever reason. The addresses are then not returned back to the source property (the addresses remain original).
# pattern matches only addresses with format "*.*#domain.com" --> <anythingButDotOr(at)>.<anythingButDotOr(at)>#domain.com
$newPrimaryAddressMatchPattern = '(?i)^(?:smtp:[^\.\#]+\.[^\.\#]+#domain1\.com)$'
Get-ADUser -Filter * -SearchBase 'OU=users_test,OU=Test,DC=test,DC=local' -Properties name, mail, ProxyAddresses |
ForEach-Object {
# flag to avoid further processing after first match
$userDone = $false
$proxies = $_.ProxyAddresses |
ForEach-Object {
$proxyAddress = $_ -replace '^SMTP', 'smtp'
if (!$userDone -and $proxyAddress -match $newPrimaryAddressMatchPattern) {
$proxyAddress -replace '^smtp', 'SMTP'
$userDone = $true
} else {
$proxyAddress
}
}
if (!$userDone) {
# if no address matched the pattern required for setting the new primary one
Write-Warning "Unable to set new primary address for $($_.UserPrincipalName) | $($_.CanonicalName)!"
} else {
$_.ProxyAddresses = $proxies
}
#Set-ADUser -instance $_
Write-Host $proxies
}

Proxyaddresses added to the multivalued attribute not appearing on seperate lines

I am using the following script to create a spreadsheet of users and proxyaddress values (matching a string) as a record prior to removing them, in the case of an emergency I want to be able to put the values back.
The script works well if there is only a single entry per user, the issue is seen when a user has more than one entry. The problem is that when I attempt to revert back to the original values, when viewed through an attribute editor the proxyaddresses are appearing all on one line instead of a separate line for each proxy address. I am not sure if the fault is on the collection script, or the script I am using to set the values. To collect the values I am running the following:
$entry = "*test.com"
$users = get-content "Testusers.txt"
$date = get-date
$mydata = #()
$domain = [system.environment]::UserDomainName
$attribute = "Proxyaddresses"
$report = "{0}_{1}_{2:HHmm_dd-MM-yyyy}.txt" -f $attribute,$domain,$date
$px = $users | Get-ADUser -Properties proxyaddresses -server $domain
foreach ($user in $px){
if ($user.proxyaddresses -like $entry){
$name = $user.samaccountname
$proxyaddresses = $user.proxyaddresses -like $entry
$mydata += New-Object PSObject -Property #{
Samaccountname = $name
Proxyaddresses = $proxyaddresses
}
}
}
$mydata | select samaccountname,#{ l = "Proxyaddresses"; e = {$_.Proxyaddresses } }| export-csv "$PWD\$report" -NoTypeInformation -Append
To return the values back to the original state I am running the following:
$csv = import-csv "Proxyaddresses_Domain_1201_05-04-2017.csv"
$domain = [system.environment]::UserDomainName
$attribute = "Proxyaddresses"
foreach ($line in $csv){
$user = $line.samaccountname
$proxyaddresses = $line.proxyaddresses
Get-aduser $User -server $domain -Properties $attribute | Set-ADUser -add
#{$attribute = $proxyaddresses} -server $domain
}
I have tried various things such as in the collection script
$proxyaddresses = $line.proxyaddresses -join ","
I have also tried the same tactic in the script used to set the values but instead of creating a new line it removes a space between the entries on the same line when viewed through an AD attribute editor.
I have also tried to change
$proxyaddresses = $user.proxyaddresses -like $entry
to the following however this did not solve the problem
$proxyaddresses = ([Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]$user.proxyaddresses -like $entry
HELP!!!
When you are exporting the data, change your select statement from this
select samaccountname,#{ l = "Proxyaddresses"; e = {$_.Proxyaddresses } }
to this
select samaccountname,#{Name="Proxyaddresses";Expression={$_.proxyaddresses -join "*"}}
Which will convert the ADPropertyValueCollection to a * separated string. If you are exporting to a CSV, you don't want to use a comma as the join separator as it will mess up the CSV. I've used a * in the example, as exchange uses colons and semi colons already.
Then when you re-import it, just make sure to convert the * seperated string back into a string array, and use -replace instead of -add
Set-ADUser -replace #{$attribute = $proxyaddresses.Split("*")} -server $domain