Replace legacyExchangeDN value in ADSI via PowerShell - powershell

I'm looking to correct a small issue in AD where the field I mentioned has a space in the value.
For example:
legacyExchangeDN : /o= First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=6131a3a42ca946b98cc146345cfd0c2e-Ron E
Should look like this:
legacyExchangeDN : /o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=6131a3a42ca946b98cc146345cfd0c2e-Ron E
This is affecting multiple users and I can manually update the value in the AD Attribute Editor, but I was hoping to be able to automate this.
Here's what I have so far:
#List all users to check existing values
$ADUserList = Get-ADUser -Filter* -Properties * | fl name, EmailAddress, legacyExchangeDN
#Replace value with space with value without space
Set-ADUser reyer -Replace #{legacyExchangeDN="/o= ","/o="}
The second line produces the following error:
Set-ADUser : Multiple values were specified for an attribute that can have only one value
At line:1 char:1
+ Set-ADUser reyer -Replace #{legacyExchangeDN="/o= ","/o="}
It feels like I'm missing something easy here, but I cannot find it. I am testing this with one user. I would like to replace the value against all remaining users in one script once tested. If I could target just the users that match the criteria of having a space in the field that would surely be helpful for either storing it or piping that over.
My final result would look like:
Target users with a space(s) present in the text value of the legacyExchangeDN → /o=
Replace just the /o= with /o= while preserving the remainder of the text value → /o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=6131a3a42ca946b98cc146345cfd0c2e-Ron E

$Users = Get-ADUser -Filter { legacyExchangeDN -like '*/o= *' } -Properties #('name','EmailAddress','legacyExchangeDN')
ForEach ($User in $Users)
{
$Replace = $User.legacyExchangeDN -replace '/o=\s','/o='
Set-ADUser -Identity $User -Replace #{ legacyExchangeDN = $Replace }
Write-Host ('Updated "{0}" ({1}) to "{2}"' -f
#($User.name,$User.EmailAddress,$Replace))
}
This will be faster as well. The mantra is: Filter Left, Format Right. Set-ADUser takes an ADUser object. -Replace takes a [HashTable] where you're assigning a new value to the named property (as it appears in LDAP).
(update: removed pipeline for foreach)
To implement logging, replace Write-Host with:
'Updated "{0}" ({1}) to "{2}"' -f #($User.name,$User.EmailAddress,$Replace) |
Out-File -FilePath "$env:UserProfile\ADChanges.txt" -Append

Related

Working with a list of AD 'displayNames' in Powershell. How to indicate which users were not found?

I have written enough PS code to go through a list of displayNames (e.g "John Smith", "Taylor Hanson" - all stored on seperate lines of a txt file) to spit back enough data into another text file that can be used for mailmerge etc. Convincing thousands of employees to simply update Windows is like breaking stones! It has to be automatted to some degree...
Here is the code... the functions that let the user open a specific text file and later save are out of view...
$displayname = #()
$names = get-content $FileIN
foreach ($name in $names) {
$displaynamedetails = Get-ADUser -filter { DisplayName -eq $name } | Select Name, GivenName, Surname, UserPrincipalName
$displayname += $displaynamedetails
}
$displayname | Export-Csv -NoTypeInformation -path $fileOUT -Encoding UTF8
From time to time, a name might be spelled incorrectly in the list, or the employee may have left the organisation.
Is there any way that a statement such as 'Not Found' can be written to the specific line of the text file if an error is ever made (so that an easy side-by-side comparison of the two files can be made?
For most of the other solutions I've tried to find, the answers are based around the samAccoutName or merging the first and last names together. Here, i am specifically interested in displaynames.
Thanks
You can give this a try, since -Filter or -LDAPFilter don't throw any exception whenever an object couldn't be found (unless you're feeding a null value) you can add an if condition to check if the variable where the AD User object is going to be stored is not null and if it is you can add this "not found" user into a different array.
$domain = (Get-ADRootDSE).DefaultNamingContext
$names = Get-Content $FileIN
$refNotFound = [System.Collections.Generic.List[string]]::new()
$displaynamedetails = foreach($name in $names)
{
if($aduser = Get-ADUser -LDAPFilter "(DisplayName=$name)")
{
$aduser
continue
}
$refNotFound.Add(
"Cannot find an object with DisplayName: '$name' under: $domain"
)
}
$displaynamedetails | Select-Object Name, GivenName, Surname, UserPrincipalName |
Export-Csv -NoTypeInformation -path $fileOUT -Encoding UTF8
$refNotFound # => Here are the users that couldn't be found
Side note, consider stop using $displayname = #() and += for well known reasons.
As for AD Cmdlets, using scriptblock based filtering (-Filter {...}) is not supported and even though it can work, it can also bring you problems in the future.

Adding a ROW for missing Attribute values to Export-CSV

I using the following POWER SHELL script, to extract ( to csv ) managers name , from a "Manager" user attribute.
#This script, , Exports the Manager name of the employee`s in the TXT file.
# users.txt file - contains a simply list of user names ( samaccount-names )
Get-Content D:\powershell\permmisions\Users.txt | Foreach-Object {
Get-ADUser -Identity $_ -Properties Manager | Select-Object name, Manager | Export-Csv D:\Powershell\ADuserinformation\Export-Managers-of-specific-users.csv
-Append
}
The challenge i am facing, is when is on the exported CSV file,
the list "SKIPS" blank value-fields,In case there is no manager set for the user.
And a ROWS is not created , where MANAGER is missing.
What i would like to do , is the script to enter a charcter ( ~ ) for example, where, value is blank.
That way , a row will be created for the blank MANAGER value, on the CSV file
Please help ,
Thanks all in advance.
Note: At least the Name property should exist on all AD users retrieved, so you would get a row even for users where Manager is empty, but with an empty Manager column. If you do need to deal with possibly not all users named in Users.txt actually existing, see Theo's helpful answer.
The simplest approach is to use a calculated property:
Get-ADUser -Identity $_ -Properties Manager |
Select-Object Name, #{ Name='Manager';
Expression={ if ($_.Manager) { $_.Manager } else { '~' } } }
Note:
It is common to abbreviate the key names of the hashtable that defines the calculated property to n and e.
The if statement takes advantage of the fact that an empty string (or $null) evaluates to $false in a Boolean context; for an overview of PowerShell's implicit to-Boolean conversion, see the bottom section of this answer.
In PowerShell [Core] 7.0 or above, you could additionally take advantage of the ternary operator (<condition> ? <valueIfTrue> : <valueIfFalse>) to further shorten the command:
# PSv7+
Get-ADUser -Identity $_ -Properties Manager |
Select-Object Name, #{ n='Manager'; e={ $_.Manager ? $_.Manager : '~' } }
Note: If $_.Manager were to return $null rather than the empty string ('') if no manager is assigned, you could use ??, the PSv7+ null-coalescing operator instead: $_.Manager ?? '~'
Not concise at all, but this allows you to insert more properties of interest in your report, and does some error-checking if the user listed in your input file does not exist:
$report = foreach ($account in (Get-Content D:\powershell\permmisions\Users.txt)) {
$user = Get-ADUser -Filter "SamAccountName -eq '$account'" -Properties Manager -ErrorAction SilentlyContinue
if ($user) {
if (!$user.Manager) { $mgr = '~' }
else {
# the Manager property is the DistinghuishedName for the manager.
# if you want that in your report, just do
$mgr = $user.Manager
# if you want the Name for instance of that manager in your report,
# comment out the above line and do this instead:
# $mgr = (Get-ADUser -Identity $user.Manager).Name
}
# now output an object
[PsCustomObject]#{
UserName = $user.Name
Manager = $mgr
}
}
else {
Write-Warning "User '$account' does not exist"
}
}
# output on screen
$report | Format-Table -AutoSize
# output to CSV file
$report | Export-Csv -Path 'D:\Powershell\ADuserinformation\Export-Managers-of-specific-users.csv' -NoTypeInformation

Mix found values and error msg in csv - Get-ADUser search

I would like to take a csv of e-mail addresses and find users that match those addresses. Output should be either the found user info OR if a matching user is not found a line that puts the searched for e-mail address then "Not Found"
$base_path = "C:\scripts\validate_users\"
$source_file = "input_emails.csv"
$out_file = "results.csv"
#read the file, look them up
$users = Import-csv -Path ($base_path + $source_file) -delimiter ";" | ForEach {
try {
Get-ADUser -Filter "EmailAddress -eq '$($_.email)'" -Properties EmailAddress
}
catch {
"No user for" + '$_.email'
}
}
# Output the resultant collection to a csv file
$users | Export-csv -Path ($base_path + $out_file)
Which gives me all the found records and no error messages.
I'd like to avoid making $users into an array and adding a value there. Is there a way to add in-line "searchedforuser#fakedomain.com NOT FOUND" inline with the results I get now.
Input is along the lines of
joesmith#ourdomain.com
janejones#ourdomain.com
freddielee#ourdomain.com
guywhoquit#ourdomain.com <== won't find this one
realuser#ourdomain.com
Right now the output is just the results for the four found users with no indication the "guywhoquit#ourdomain.com" was ever in the original list
Sorry if this is a newb question. I am a ps newb, but I searched for quite a bit and I'm missing if a similar question has already been answered.
Since you're using Get-AdUser with the -Filter parameter, it will simply return $null if no matching user is found (assuming the -Filter argument is well-formed) - it won't report an error.
Therefore, check the Get-ADUser's output to see if a user was found.
The -ov (-OutVariable) common parameter allows you to capture a cmdlet's output in a variable (independently of its output behavior), which you can inspect later:
$base_path = "C:\scripts\validate_users"
$source_file = "input_emails.csv"
$out_file = "results.csv"
Import-csv -Path (Join-Path $base_path $source_file) -delimiter ";" | ForEach {
# Get and output the user for the email address at hand;
# also store the output in variable $user, via `-ov user`
Get-ADUser -Filter "EmailAddress -eq '$($_.email)'" -Properties EmailAddress -ov user
if ($user.Count -eq 0) { # User not found?
# Emit a dummy object with an .EmailAddress property
# whose value indicates that the user wasn't found.
# This will show up in the CSV file as a row with all columns
# except the "EmailAddress" one empty.
[pscustomobject] #{ EmailAddress = "No user for $($_.email)" }
}
} | Export-csv -Path (Join-Path $base_path $out_file)
Note: The reason that just emitting string "No user for" + '$_.email' to the output stream wouldn't be enough is that Export-Csv locks in the columns it outputs based on the 1st input object.
A [string] instance has no properties in common with an AD users object, so you'd get a CSV row without any values.
By constructing a dummy custom object with an .EmailAddress property ([pscustomobject] #{ EmailAddress = "..." }), that property value will show up in the file (though all other column values will be empty).
Your problem here is that Powershell only catches "Terminating exceptions" to solve this you could try either of this 2 following modifications:
Get-ADUser -Filter "EmailAddress -eq '$($_.email)'" -Properties EmailAddress -ErrorAction Stop #This will only affect this cmdlet.
Or
$ErrorActionPreference = 'Stop' #This affects every cmdlet execution you have after this line.

Powershell Pipeline Argument not gettings passed to Set-ADUser

I've been banging my head against the wall for the past few hours, I'm sure it's because I don't quite understand something about how #{} and $_ work.
First the code:
Get-ADUser username -Properties mail | Set-ADUser -replace #{"proxyaddresses"="SMTP:"+$_.mail}
As you can see I'm trying to update the proxyaddresses fields with the user's email address.
Instead only the string is pulled:(output: proxyaddresses : {SMTP:}) and the pipeline is ignored, I'm assuming it's because it's empty for some reason, but it's not clear to me why.
I've tried variations such as "proxyaddresses="SMTP:$($_.mail)" I tried using default properties that are always sent with Get-ADUser such as UserPrincipalName
I know that something like this is possible because of this http://www.itprotoday.com/management-mobility/more-flexible-active-directory-one-liner and mutiple answers on SO using some variation of the linked example.
When I assign a variable to SMTP:$_.mail and then use that in the field instead like so:
Get-ADUser username -Properties mail | %{ $smtp = "SMTP:"+$_.mail
$_|Set-ADUser -replace #{"proxyAddresses"=$smtp}
This works (output: proxyaddresses :{SMTP:emailaddress#domain.com}). If I leave out the string like so: #{"proxyaddresses"=$_.mail}
I get the following error:
Set-ADUser : Cannot bind parameter 'Replace' to the target. Exception setting "Replace": "Object reference not set to an instance of an object."
At line:1 char:58
I'm not sure what this means.
I'd like some variation of my initial idea to work, but I'll settle for the workaround using an extra variable if there's no other way.
EDIT: There seems to be some confusion about what I'm asking, so I'll clarify:
Is there a way to use the pipeline variable $_ without a script block inside a hashtable, (inside a script block requires double piping like #TheIncorrigible1 suggested in his first answer.)?
EDIT: Based on this it seems this should not be having any issues.
You need to use ForEach-Object to access the pipeline in the way you're trying, otherwise it doesn't know what your pipeline object ($_) is:
Get-ADUser -Identity username -Properties mail |
ForEach-Object {
$_ | Set-ADUser -Replace #{ 'proxyaddresses' = 'SMTP:' + $_.mail }
}
Or the -PipelineVariable common parameter which explicitly assigns $_ to a variable:
Get-ADUser -Identity username -Properties mail -PipelineVariable user |
Set-ADUser -Replace #{ ProxyAddresses = "SMTP:$($user.mail)" }
ProxyAddresses is an array where the Primary Email address is set like SMTP:primary#example.com but there can and will be other elements there too like alias email addresses (that have the lowercase smtp: prefix), SIP: addresses etc.
NEVER try to simply overwrite whatever is already there by a single string found in the mail attribute of the user object, but merge them with the ones you want to add. Selectively replace the ones you want to be changed and build an array of valid addresses.
Basically you do
$oldErrorAction = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
$user = Get-AdUser -Identity $SAMAccountName -Properties mail
$primaryEmailAddress = $user.mail
$externalAddress = "smtp:<WHATEVER ALIAS YOU WOULD LIKE FOR THE USER">
$mailProxies = #("SMTP:$primaryEmailAddress", "smtp:$externalAddress")
# add more to this array if need be
$newProxies = #{'ProxyAddresses' = $mailProxies}
try {
$user | Set-ADUser -Clear ProxyAddresses
$user | Set-ADUser -Add $newProxies
}
catch {
Write-Warning "Could not set ProxyAddresses: $($_.Exception.Message)"
}
$ErrorActionPreference = $oldErrorAction
You may want to try set the variable first and then call on it in the script.
something like this,
$user = Get-ADUser username -Properties mail | select-object mail
Then write your script and call on the variable you may need to use foreach with a if statement to get it to function the way you want.

PowerShell foreach loop with embedded if statement yields "cannot accept 'True' as positional parameter"

I'm trying to write a simple PS line to take the exported .csv of AD groups in particular "regions" we have set up, then take the GroupScope (universal vs. global), and depending on the scope of the group, write the "department" attribute as either "Universal" or "Global." The reason for doing so is to help identify between the 2 scopes within SharePoint.
$uni="Universal"
Import-csv \\usershare\user\me\output\groups.csv | foreach {Get-ADGroup -Identity $_.Name -Properties * | Set-ADGroup if($_.GroupScope -eq $uni){-replace #{department=$uni}}}
This is returning the following error message though:
"A positional parameter cannot be found that accepts the argument 'True.'"
I'm probably missing something simple here but I just started out and I'm self-teaching by trial and error mostly. Thanks for any help you can provide!
You're use of the if block is not valid for what you are trying to accomplish, it will only return True or False and Set-ADGroup will not know what to do with that hence the error.
Try this:
$uni="Universal"
$csv = Import-Csv -Path '\\usershare\user\me\output\groups.csv'
foreach($i in $csv)
{
if($i.GroupScope -eq $uni)
{
Get-ADGroup -Identity $i.Name -Properties 'department' |
Set-ADGroup -Replace #{department=$uni}
}
}
I've set properties to only pull department as a smaller scope will speed up the query.