Updating user ProxyAddresses - powershell

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

Related

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
}

Disable users from valid list

I've got a list of valid users provided by HR. The formatting was not cool, so I managed to get a new file like I wanted: one column, on each line the samaccountname (1st letter of firstname and name).
My file looks like this:
bgates
sjobs
bmarley
epresley
etc.
I'd like to disable users who are NOT in this list. I guess I have to deal with some if stuff, but I don't know how to.
#HariHaran, i have tried this:
#this part works fine
$list = Import-Csv .\listadnames2.csv -Delimiter ";"
$lol =
ForEach ($user in $list)
{
$user.prenom[0] + $user.nom
}
$lol | Out-File .\samaccountnames.csv
$validusers = Import-Csv .\samaccountnames.csv
$fullusers = Get-ADUser -Filter * -SearchBase "OU=USERS,DC=domain,DC=com" -ResultPageSize 0 -Prop samaccountname | Select samaccountname
foreach ($u in $validusers)
if ($u -match $fullusers) {continue} else
{
Set-ADUser -Identity $($._) -Enabled $false -whatif
}
The users list (samaccountnames.csv) you create in $lol is not a CSV file, but simply a text file with all constructed usernames each on a separate line.
Therefore you should read the file using
$validusers = Get-Content .\samaccountnames.csv instead of $validusers = Import-Csv .\samaccountnames.csv.
Then you'll have an array of samaccountnames to work with.
Next, I wonder why you use -ResultPageSize 0.. The default setting is 256 objects per page, so I can only imaging you could need this value to be higher than this default, not less.
(see the docs)
Taken from the part where you read the samaccountnames file, I think this will do the job:
$validusers = Get-Content .\samaccountnames.csv
# property 'SamAccountName' is returned by default as are
# 'DistinguishedName', 'Enabled', 'GivenName', 'Name', 'ObjectClass', 'ObjectGUID', 'SID', 'Surname' and 'UserPrincipalName'
# get the user objects from AD and loop through them to see if they need to be set disabled
Get-ADUser -Filter * -SearchBase "OU=USERS,DC=domain,DC=com" | ForEach-Object {
# the $_ automatic variable now holds an AD user object
# or use if($_.SamAccountName -notin $validusers). Only for PowerShell version 3.0 and up
if ($validusers -notcontains $_.SamAccountName) {
$_ | Set-ADUser -Enabled $false -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]

PowerShell trying to list users who have group membership similar string

I have a function that helps me grab over 20k users in our AllUsers group (gets ObjectClass,Name,SamAccountname,DistinguishedName). I'm trying to list anyone who has CBA-* groups, but the loop seems to repeat the output a couple of times per user (after the 1st one), when I only want one iteration (many CBA-* possibilities). Here's what I have. Also, I get errors "Get-ADPrincipalGroupMembership : The server was unable to process the request due to an internal error." Any ideas why that would happen? I'm not sure I can see why my code repeats maybe twice, but then seems to move on to the next user just fine?
ForEach ($User in $Users) {
Get-ADPrincipalGroupMembership -Identity $User.SamAccountName |
Select Name |
Where-Object {$_.Name -like 'CBA-*'} |
ForEach-Object { $User_MemberOf += #($_.Name) }
ForEach ($Group in $User_MemberOf) {
New-Object PSObject -Property #{
SID = $User.SamAccountName
Name = $User.name
Group = $Group
} | Export-Csv -Path $logs -NoTypeInformation -Append
}
}
For each loop iteration, you continue to assign values to $User_MemberOf with the addition operator (+=) meaning that for each new user it gets "sanded" more and more with the previous users' memberships.
Two ways to avoid:
Initialize $User_MemberOf as an empty array at the start of each loop iteration:
foreach($User in $Users){
$User_MemberOf = #()
Get-ADPrincipalGroupMembership -Identity $User.SamAccountName |Select -Expand Name |Where-Object {
$_ -like 'CBA-*'
} |ForEach-Object {
$User_MemberOf += $_
}
foreach($Group in $User_MemberOf){
# export to CSV
}
}
Assign output directly from pipeline:
foreach($User in $Users){
$User_MemberOf = Get-ADPrincipalGroupMembership -Identity $User.SamAccountName |Select -Expand Name |Where-Object {
$_ -like 'CBA-*'
} |ForEach-Object {
$User_MemberOf += $_
}
foreach($Group in $User_MemberOf){
# export to CSV
}
}

Trying to rename several account types at once based on current displayName

This morning some awesome people helped me make a script to move user accounts based on their displayName to a certain OU. I tested and it worked. I cannibalized the script to make another one that will rename the same accounts based off of the same criteria. I've gone through several errors but basically it all boils down to "I am having an identity crisis!". I can't seem to figure out exactly what I need to input as the $Identity. Here is what I have:
Import-Module ActiveDirectory
$Renames = #(
#{
Filter = 'DisplayName -like "*Supply*"'
NewName = "Supplies"
},
#{
Filter = 'DisplayName -like "*Accountant*"'
NewName = "Accounting"
}
) | ForEach-Object {New-Object -TypeName PSCustomObject -Property $_}
$OriginOU = "OU=Test,OU=Standard Users,OU=Domain Users,DC=com"
foreach ($Rename in $Renames) {
Get-ADUser -SearchBase $OriginOU -Filter $Rename.Filter -Properties displayName |
Where-Object {($_.Enabled -eq 'True') -and ($_.DistinguishedName -notlike '*DontTouch*')} |
%{Set-ADUser $_ -DisplayName {$_.DisplayName -replace '(.EPSILON ).+',"`$1$Rename.NewName"}}
}
You can't use the current object variable ($_) if you have Set-ADUser read directly from the pipeline. And since Set-ADUser apparently doesn't play nice with scriptblock arguments, you have to put the statement in a loop:
... | % { Set-ADUser $_ -DisplayName ($_.DisplayName -replace '(.EPSILON ).+',"`$1$($Rename.NewName)") }
Note that if you want to expand object properties inside a string you have to put $Rename.NewName in a subexpression ($()), otherwise the whole object $Rename would be stringified and the string ".NewName" would be appended to it.