Powershell ForEach replace (Bulk change primary SMTP in AD) - powershell

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
}

Related

Powershell if condition returning no results when some exist

The code block below executes without the CSV being generated suggesting it has no return. However the conditions given are definitely valid for at least one object.
A Mail Enabled Security group in O365 has the "SecurityEnabled" property set to true, the "MailEnabled" property set to true and the property "GroupTypes" is an empty array? string? "{}" whatever the curly brackets are supposed to represent but they're empty.
$Groups = Get-MgGroup -Property "Members" -All
foreach($group in $Groups){
if ( (($group.SecurityEnabled) -and ($group.MailEnabled)) -and ($group.GroupTypes -ne 'Unified')){
$members = Get-MgGroupMember -GroupId $group.Id
[PSCustomObject]#{
'Display Name' = $group.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
} | Export-Csv -path "$OutputFolder\MailEnabledSecurityGroups.csv" -Append -NoTypeInformation
}
continue
}
The GroupTypes property from the MicrosoftGraphGroup instance is an array, in this case you want to use containment operators, more specifically -notin or -notcontains, the condition would look like:
# with `-notcontains`
if ($group.SecurityEnabled -and $group.MailEnabled -and $group.GroupTypes -notcontains 'Unified') {
# with `-notin`
if ($group.SecurityEnabled -and $group.MailEnabled -and 'Unified' -notin $group.GroupTypes) {
As for why it was not working before, -ne and -eq can act as a filter when the LHS (left hand side) of the comparison is an array and because $group.GroupTypes was an empty array, the comparison returned null and the if condition evaluated to false because of this.
$obj = [pscustomobject]#{
GroupTypes = #()
}
$obj.GroupTypes -ne 'Unified' # => null
$obj = [pscustomobject]#{
GroupTypes = #('something else')
}
$obj.GroupTypes -ne 'Unified' # => something else
As aside, it's probably a better idea to export the output all at once instead of appending to the CSV file on each loop iteration (Disk I/O is expensive and will slow down your script a lot!):
Get-MgGroup -Property "Members" -All | ForEach-Object {
if ($_.SecurityEnabled -and $_.MailEnabled -and 'Unified' -notin $_.GroupTypes) {
$members = Get-MgGroupMember -GroupId $_.Id
[PSCustomObject]#{
'Display Name' = $_.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
}
}
} | Export-Csv -path "$OutputFolder\MailEnabledSecurityGroups.csv" -NoTypeInformation
Filtering on Azure side may increase the speed of your code and also reduce the amount of conditions being evaluated on client side:
Get-MgGroup -Property "Members" -Filter "securityEnabled eq true and mailenabled eq true" | ForEach-Object {
if ('Unified' -notin $_.GroupTypes) {
# code here
}
}

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

How to modify the Powershell Calculated Property with If Else statement for SMTPAddresses? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I am wanting to modify the Powershell to list users from Azure AD to display the information correctly in the SMTP Address Column.
The below script is correct, I just need to make some changes like below if possible:
If the UserPrincipalName value contains Company1.onmicrosoft.com, then SMTP Address column should show everything as it is, do not process anything.
If the UserPrincipalName value does NOT contain Company1.onmicrosoft.com, then SMTP Address column should NOT display the *#Company1.mail.onmicrosoft.com or *#Company1.onmicrosoft.com, so only the normal SMTP protocol address.
The Licenses column should NOT display the prefix Company1:EnterpriseE1, but just EnterpriseE1.
So far I can only display the ProxyAddresses with SMTP like:
#{Label = 'SMTP Address'; Expression = { ($_.proxyAddresses | Where-Object { $_ -like "*smtp*" }) -replace 'smtp:' -join ';' } }
Here it is my full script:
#Import Module
If (!(Get-Module "*MSOnline*")) {Import-Module MSOnline}
If (!(Get-Module "*Exchange*")) {Import-Module $((Get-ChildItem -Path $($env:LOCALAPPDATA + "\Apps\2.0\") -Filter Microsoft.Exchange.Management.ExoPowershellModule.dll -Recurse).FullName | ?{ $_ -notmatch "_none_" } | select -First 1)}
#Set admin UPN
$UPN = 'Global.Admin#domain.com'
#This connects to Azure Active Directory & Exchange Online
Connect-MsolService
$EXOSession = New-ExoPSSession -UserPrincipalName $UPN
Import-PSSession $EXOSession -DisableNameChecking -AllowClobber
$startsWith = #(
'Test'
'Sync_'
)
$endsWith = #(
'365'
'\$'
'svc'
'Sync'
'user'
)
$pattern = '^({0})|({1})$' -f $($startsWith -join '|'), $($endsWith -join '|')
# Member Outputs for Microsoft.Online.Administration.User based on https://learn.microsoft.com/en-us/powershell/module/msonline/get-msoluser?view=azureadps-1.0
$allUsers = #()
$allUsers = Get-MsolUser -All -EnabledFilter EnabledOnly | Where-Object {
($_.UserPrincipalName -notmatch $pattern) -and
($_.UserPrincipalName -notlike '*#EXT#*') -and
($_.DisplayName -notmatch 'Admin|Calendar|Room|Prod|Account|Fax|Team|Office|Test|User')
} | Select-Object FirstName, LastName, UserPrincipalName, #{Label = 'SMTP Address'; Expression = { ($_.proxyAddresses | Where-Object { $_ -like "*smtp*" }) -replace 'smtp:' -join ';' } }, AlternateEmailAddresses, UsageLocation, isLicensed, Licenses, PasswordNeverExpires, BlockCredential
$allUsers | Out-GridView
I'm not sure what you mean by normal SMTP protocol address.
If the UserPrincipalName value does NOT contain
Company1.onmicrosoft.com, then SMTP Address column should NOT display
the *#Company1.mail.onmicrosoft.com or *#Company1.onmicrosoft.com, so
only the normal SMTP protocol address.
However, it's pretty straight forward to add logic to a calculated property. Hopefully this example can give you the groundwork.
It can be a little messy so I like to park the Hash table expression definition in a separate variable to later reference in the Select-Object command:
...
$SMTPExpression =
#{
Label = 'SMTP Address'
Expression =
{
If( $_.UserPrincipalName -match 'company1,onmicrosoft.com$')
{
$_.ProxyAddresses.Where( { $_ -match '^smtp:' } ) -replace 'smtp:'
}
Else
{
# Add some value or expression here...
$_.ProxyAddresses.Where( { $_ -match '^smtp:' } )
}
}
}
$allUsers = Get-MsolUser -All -EnabledFilter EnabledOnly |
Where-Object {
($_.UserPrincipalName -notmatch $pattern) -and
($_.UserPrincipalName -notlike '*#EXT#*') -and
($_.DisplayName -notmatch 'Admin|Calendar|Room|Prod|Account|Fax|Team|Office|Test|User') } |
Select-Object FirstName, LastName, UserPrincipalName, $SMTPExpression, AlternateEmailAddresses, UsageLocation, isLicensed, Licenses, PasswordNeverExpires, BlockCredential |
$allUsers | Out-GridView
I did change your where statement a little, again just trying to make it a little shorter and a couple of other minor things (slight reformat so I could work easier....).
I don't have an environment to test this in, but the strategy is solid.

Updating user ProxyAddresses

I try to update AD user account ProxyAddresses property. I've read many topics about this and applied one of the suggested approach (this one) but it doesn't work for me. Why?
I use a following bit of code (it's a part of script updating more users data):
$ADUser = SearchADUser -Kogo "sAMAccountName -eq '$($WzorUser.sAMAccountName)'"
...
1.$ProxyOK = $false
2.$Proxies = $ADUser.ProxyAddresses
3.$Proxies | ForEach-Object {
4. $_ = $_ -replace 'SMTP', 'smtp'
5. if ($_ -match $NoweMail) {
6. $_ = $_ -replace 'smtp', 'SMTP'
7. $ProxyOK = $true
8. }
9.}
10.if (!($ProxyOK)) { $Proxies += ("SMTP:$($NoweMail)") }
...
if (!([string]::IsNullOrEmpty($Proxies))) {
$AttrToReplace.Add("ProxyAddresses", $Proxies)
Set-ADUser -Identity $ADUser.sAMAccountName -Server $ADDC #Attr #-PassThru -WhatIf
When looping on Proxies, its elements are properly processed: letters are lowercased for all and uppercased if the new mail was already present.
But Proxies is not changed. Do each element needs to be saved in some way or replaced in the object?
Update 2020-04-02
Because supporter effort (#Theo once again thanks for Your assist) focus on replacing method I try to more detailed explain my problem.
Target user proxyAddresses values (it's a rare case when women come back to her maiden name alredy recorded in AD):
SMTP:anna.nowak22#lp.pl
smtp:a.b#moc.com
Initial values (line 2):
[DBG]: PS X:\>> $Proxies
smtp:anna.nowak22#lp.pl
SMTP:a.b#moc.com
While looping on every proxy element (line 9):
[DBG]: PS X:\>> $_
SMTP:anna.nowak22#lp.pl
[DBG]: PS X:\>> $Proxies
smtp:anna.nowak22#lp.pl
SMTP:a.b#moc.com
[DBG]: PS X:\>> $_
smtp:a.b#moc.com
[DBG]: PS X:\>> $Proxies
smtp:anna.nowak22#lp.pl
SMTP:a.b#moc.com
As can be seen $Proxies doesn't reflect changes.
If there was only one ProxyAddresses value not equal with new mail, new mail was added as SMTP to the existed one which is leaved also as SMTP (two primary ProxyAddresses).
I tried to create a new variable and assign to it each value respectively but I don't know how to handle it.
$newProxies = $null
$Proxies | ForEach-Object {
$_ = $_ -creplace 'SMTP', 'smtp'
if ($_ -match $NoweMail) {
$_ = $_ -creplace 'smtp', 'SMTP'
$ProxyOK = $true
}
$newProxies.add($_)
}
Above generates an error
You cannot call a method on a null-valued expression
$newProxies += $_ creates one string SMTP:anna.nowak22#lp.pl
smtp:a.b#moc.com which is added as single ProxyAddress.
As I noted $Proxies is a special AD object and I don't know how to create an object of such type and how to add new elements to it.
As commented, here more readable as answer.
In your code, you add the modified $Proxies array to a hashtable (i think) called $AttrToReplace. However, in the final Set-ADUser command, you are not using this, but instead splat using a variable #Attr.
To update the multivalued attribute ProxyAddresses, change the code after the three dots in your question from:
if (!([string]::IsNullOrEmpty($Proxies))) {
$AttrToReplace.Add("ProxyAddresses", $Proxies)
Set-ADUser -Identity $ADUser.sAMAccountName -Server $ADDC #Attr #-PassThru -WhatIf
into:
# no need to test if $Proxies is an empty array, because
# it will at the very least have "SMTP:$($NoweMail)"
Set-ADUser -Identity $ADUser.SamAccountName -Clear ProxyAddresses
Set-ADUser -Identity $ADUser.SamAccountName -Add #{proxyAddresses = $Proxies | ForEach-Object { "$_" }}
# or do this on one line:
# Set-ADUser -Identity $ADUser.SamAccountName -Replace #{proxyAddresses = $Proxies | ForEach-Object { "$_" }}
Update
From your comments, your code does leave room for duplicate proxy addresses.
You can get this list, replace all existing SMTP: by smtp: and add the new primary email address to it like this:
# get the current ProxyAddress values for this user, change all values
# that begin with uppercase 'SMTP:' to lowercase 'smtp:'.
# skip any proxy that is equal to "smtp:$NoweMail", add the $NoweMail
# preceeded by 'SMTP:' to the list and sort or select unique
$Proxies = #($ADUser.ProxyAddresses -replace '^SMTP:', 'smtp:' |
Where-Object { $_ -ne "smtp:$NoweMail" }) + "SMTP:$NoweMail" | Sort-Object -Unique
# set the new proxy addresses in the user attribute
# no need to test if $Proxies is an empty array, because
# it will at the very least have "SMTP:$($NoweMail)"
Set-ADUser -Identity $ADUser.SamAccountName -Clear ProxyAddresses
Set-ADUser -Identity $ADUser.SamAccountName -Add #{proxyAddresses = [string[]]$Proxies}
# or do this on one line:
# Set-ADUser -Identity $ADUser.SamAccountName -Replace #{proxyAddresses = [string[]]$Proxies}
Note that inside the hash to Add or Replace, the LDAP name is used, so proxyAddresses with a lowercase p
#Theo. You solution works fine. Thank You for help.
I remained my approach (it suits me and works) and modified code a bit.
$ProxyOK = $false
$Proxies = $null
$Proxies = #()
$ADUser.ProxyAddresses | ForEach-Object {
$_ = $_ -creplace 'SMTP', 'smtp'
if ($_ -match $NoweMail) {
$_ = $_ -creplace 'smtp', 'SMTP'
$ProxyOK = $true
}
$Proxies += $_
}
if (!($ProxyOK)) { $Proxies += ("SMTP:$($NoweMail)") }
...
if ($Proxies) {
$AttrToReplace.Add("ProxyAddresses", [string[]]$Proxies)
}
if ($AttrToReplace.Count -gt 0) {
$Attr.Add("Replace", $AttrToReplace)
}
Set-ADUser -Identity $ADUser.sAMAccountName -Server $ADDC #Attr #-PassThru -WhatIf

Manipulating multiproperty AD attributes (ProxyAddresses)

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]