Function to generate samaccountname in PowerShell - powershell

I am trying to write a PS function that can generate SamAccountName in a specific way, I have found another question here but it is not similar to what i'm trying to do.
I need the SamAccountName to be generated like this:
UNIT + initial_of_firstName + initial_of_lastName
for example employee Jane Doe in unit FLN could have FLNjd as SamAccountName, and the function should check if that ID is taken by another user and if true then SamAccountName should be:
UNIT + initial_of_firstName + first_two_initials_of_lastName such as FLNjdo
and if that is also taken then SamAccountName should be:
UNIT + first_two_initials_of_firstName + first_two_initials_of_lastName such as FLNjado
and if that is also taken then SamAccountName should be:
UNIT + first_two_initials_of_firstName + first_two_initials_of_lastName + 1 such as FLNjado1
and from here it starts adding numbers 2, 3, 4.... as long as the function finds that the ID exists.
I only managed to extract the initials needed:
$first_of_fname = ($_.Fname).Substring(0,1).ToLower()
$first_of_lname = ($_.Lname).Substring(0,1).ToLower()
$FirstandSecond_of_fname = ($_.Fname).Substring(0,2).ToLower()
$FirstandSecond_of_lname = ($_.Lname).Substring(0,2).ToLower()
I need now to know how to generate the the SamAccountName in the above form and order.
ANy help will be very much appreciated.

You can do the following:
Function Get-SamAccountName {
Param(
[Parameter(Position=0,ValueFromPipelineByPropertyName)]
$Unit,
[Parameter(Position=1,ValueFromPipelineByPropertyName)]
$First,
[Parameter(Position=2,ValueFromPipelineByPropertyName)]
$Last
)
$fcount = 1
$lcount = 1
$inc = 1
$Sam = "{0}{1}{2}" -f $unit,$first.substring(0,$fcount).ToLower(),$last.substring(0,$lcount++).ToLower()
while (Get-AdUser -Filter "SamAccountName -eq '$Sam'") {
if ($fcount -le 2) {
$Sam = "{0}{1}{2}" -f $unit,$first.substring(0,$fcount++).ToLower(),$last.substring(0,$lcount).ToLower()
} else {
$Sam = "{0}{1}{2}{3}" -f $unit,$first.substring(0,2).ToLower(),$last.substring(0,2).ToLower(),$inc++
}
}
$Sam
}
Usage Option 1: Using Named Parameters
Get-SamAccountName -Unit 'FLN' -First 'Sam' -Last 'Wise'
Usage Option 2: Using positional parameters (unnamed)
Get-SamAccountName 'FLN' 'Sam' 'Wise'
Usage Option 3: Having A Text List Of Units and Names
$users = Get-Content users.txt
$users
FLN,Sam,Wise
HRS,John,Doe
COM,Jane,Doe
$users | ConvertFrom-Csv -Header 'Unit','First','Last' | Foreach {
$_ | Get-SamAccountName
}
The pipe to ConvertFrom-Csv can be skipped here if your file is already a CSV with the proper headers.
Explanation:
The function returns a string that represents an available SamAccountName value.
$fcount is the first name character count that we want to retrieve. $lcount is the last name character count that we want to retrieve. $inc is the number we want to append to the username if needed.
$var++ notation increments the $var value by one after its line is executed. The timing of $fcount++ and $lcount++ allows us to retrieve (1,1),(1,2),(2,2) characters from (first,last) while minimizing code. $inc++ will increment after the current $inc value is retrieved.
-f is the string format operator.
The while loop will only execute if the current created SamAccountName value is found.
Note: The code shortening complexity may or may not be worth it. It does not gain much from a performance perspective, but I do feel code complexity on this level is merely relative.

To take a different approach, instead of multiple if statements, you can use the switch statement "backwards" to find out what is $null or not available, and fall back to the default switch which is the final while iteration loop incrementing the count.
#Get substrings
$first_of_fname = ($_.Fname).Substring(0,1).ToLower()
$first_of_lname = ($_.Lname).Substring(0,1).ToLower()
$FirstandSecond_of_fname = ($_.Fname).Substring(0,2).ToLower()
$FirstandSecond_of_lname = ($_.Lname).Substring(0,2).ToLower()
#create patters to match on
$pattern1 = $UNIT + $first_of_fname + $first_of_lname
$pattern2 = $UNIT + $first_of_fname + $FirstandSecond_of_lname
$pattern3 = $UNIT + $FirstandSecond_of_fname + $FirstandSecond_of_lname
#evaluate what is available
switch($null){
(Get-ADUser -Filter "samAccountName -like '$pattern1'"){
Write-Host "$pattern1 - Free"
break
}
(Get-ADUser -Filter "samAccountName -like '$pattern2'"){
Write-Host "$pattern2 - Free"
break
}
(Get-ADUser -Filter "samAccountName -like '$pattern3'"){
Write-Host "$pattern3 - Free"
break
}
default {
$cnt = 1
while (Get-ADUser -Filter "SamAccountName -like '$pattern3$cnt'") {
$cnt++
}
Write-Host "$pattern3$cnt - Free"
break
}
}

Related

Adding a string to a extensionattribute in Powershell

I try to add value X to a Active Directory Member Y that has already some values in extensionAttribute10. How do I add a specific string into that? Also I want to scan the extensionAttribute and remove a specific entry.
Testparameters:
$ThisUser = "Testaccount01"
#{extensionAttribute10=Computer01, Computer02}
$adding = "Computer03"
$delete = "Computer01"
assuming the target is a user account:
#Replace existing value with a new one
set-aduser -identity [userSamAccountName] -replace #{extensionAttribute10=$value}
#Add new value to
set-aduser -identity [userSamAccountName] -add #{extensionAttribute10=$value}
If your target is a different object type, e.g. a computer use the related cmdlets, the syntax is equal....
As response to your comment: Sure the code does work but you need to consider the datatype of the target attribute, which is not an array it's a single value. So if you need to store multiple values there you need to transform the array into a delimited string, e.g.:
$value = #('comp1','comp2')
$valueString = $value -join ","
set-aduser -identity [userSamAccountName] -add #{extensionAttribute10=$valueString}
But be aware that this attribute has a size limit.
Ok based on your code sample, you cast the output as string array ([string[]]) but as outlined earlier you need a string and not an array:
your code
$newAttribute = [string[]]($user.extensionAttribute10 -split '[,\s]+' | ?{$_ -ne $delete}) + $add
replace with:
$valueArray = #($user.extensionAttribute10 -split ',' | ?{$_ -ne $delete})
$valueArray += $add
$valueString = $valueArray -join ","
set-aduser -identity [userSamAccountName] -replace #{extensionAttribute10=$valueString}
$ThisUser = "Test"
$delete = "Computer2"
$add = "Computer3"
$user = Get-ADUser -Identity $ThisUser -Properties extensionAttribute10
$newAttribute = [string[]]($user.extensionAttribute10 -split '[,\s]+' | ?{$_ -ne $delete}) + $add
Set-ADUser $thisUser -Replace #{extensionAttribute10=$newAttribute}

Get-ADCompter command returns back null response

I'm trying to combine a string and an int together to get the output JWang1 but everytime I run this command it keeps returning back null, any help is appreciated, thank you
$pcname = "Jwang"
$number = 1
Get-ADcomputer -filter "name -like '$pcname + $number'" | select -ExpandProperty name
The goal is to get the command to search "JWANG1", but the result keeps coming back null
Alternatively if I do this, I get a search result posted back to me
$pcname = "Jwang1"
Get-ADcomputer -filter "name -like '$pcname'" | select -ExpandProperty name
The difference here is that I am not trying to combine the string and int, but how do I get it to combine and work?
The issue is how string expansion works, and how you're using it. Right now you have:
"name -like '$pcname + $number'"
Once you sub in the values for those variables it reads like this:
"name -like 'JWANG + 1'"
There's several ways you could go about correcting this. The first is to simply remove the + from the string so it reads like this:
"name -like '$pcname$number'"
The second way is to put in a subexpression like this:
"name -like '$($pcname + $number)'"
Or you could combine them before you reference it like:
$pcname = "Jwang"
$number = 1
$combinedname = $pcname + $number
Get-ADcomputer -filter "name -like '$combinedname'" | select -ExpandProperty name
In this case you would need to use the Subexpression operator $(..) to resolve the concatenation operation:
$pcname = "Jwang"
$number = 1
"name -like '$pcname + $number'"
# Interpolates both variables without resolving the concatenation
# Results in: name -like 'Jwang + 1'
"name -like '$($pcname + $number)'"
# Resolves the expression inside `$(..)` before interpolating it
# Results in: name -like 'Jwang1'
As aside, the right operator for your query to Active Directory should be -eq instead of -like since you're not using any wildcard for your query.

Trouble with $arrayList.Remove($item)

I am having trouble with this.
What i am trying to do is I have 2 different array, and I am trying to exclude $items in $ignore from $users
I can't use where $users | where {$ignore -notcontains $_} because the string in the $ignore array may contain wildcard (e.g., admin*)
But here is the main problem, when I matched the $item that i want to remove, it removes my entire array instead of the selected value.
[System.Collections.ArrayList]$users = Get-ADUser -Filter * | Select Name
$users = $users.name
$ignore = $audArr[5] -Split(",")
foreach ($item in $ignore) {
if ($item.Contains("*")) {
foreach ($user in $users) {
$matchResult = $user -like $item
if ($matchResult -eq $true){
$users = $users.Remove($item)
}
}
}
else {
$users = $users.Remove($item)
}
}
However, I if i try to .Add it works despite showing error
Cannot convert the "3" value of type "System.Int32" to type "System.Collections.ArrayList".
At C:\users\administrator\desktop\scannerTest.ps1:326 char:17
+ $users = $users.Add($item)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : ConvertToFinalInvalidCastException
Also, since i am already asking about powershell items.. Does anyone know what how to get LogonTime, KickoffTime via powershell? Adn what is ACB?
Santiago Squarzon's helpful answer uses a good approach with regex matching. We can further enhance the regex matching to include a combination of wildcard and exact matches.
$users = 'John Doe','Wolverine','Mr. Pants','Administrator','Jane Doe','Master Administrator'
# two exact matches and one wildcard match
$ignore = 'john doe','jane doe','admin*'
$regex = '^({0})$' -f (($ignore -replace '\*','.*') -join '|')
# List users who don't match $ignore
$users -notmatch $regex # Jane Doe, John Doe, Administrator not listed
If you must use an ArrayList, you can apply the same technique as above by
removing successful matches:
[collections.ArrayList]$users = 'John Doe','Wolverine','Mr. Pants','Administrator','Jane Doe','Master Administrator'
# two exact matches and one wildcard match
$ignore = 'john doe','jane doe','admin*'
$regex = '^({0})$' -f (($ignore -replace '\*','.*') -join '|')
$users -match $regex | Foreach-Object {
[void]$users.Remove($_)
}
Explanation:
In regex, alternations (basic OR logic) are separated by |. .* greedily matches any number of characters in a string. ^ denotes the beginning of a string. $ denotes the end of a string. Putting that all together, your exact matches would look like ^exact$ and your partial matches would look like ^admin.*$ (for admin*) or ^.*admin.*$ (for *admin*). Using alternation, you can combine those matches for an OR effect with ^(admin.*|Benjamin Dover|Johnny Lawrence)$.
What you're trying to do could be simplified to something like this:
Create a test case, the array will have User00 to User10 plus Administrators and Domain Admins.
$ignore = #(
'Administrators'
'Domain Admins'
'User05'
'User10'
)
[System.Collections.ArrayList]$users = 0..10 |
ForEach-Object { [pscustomobject]#{Name="User{0:00}" -f $_} }
$users.Add([pscustomobject]#{Name="Administrators"})
$users.Add([pscustomobject]#{Name="Domain Admins"})
Now you can simply use the .Where(..) method filtering those elements of the $users array that do not match the $ignore array on one side and those that do match on the other side.
$toIgnore = $ignore -join '|'
$notIgnoredUsers, $ignoredUsers = $users.Where({$_.Name -notmatch $toIgnore},'Split')
$notIgnoredUsers
Name
----
User00
User01
User02
User03
User04
User06
User07
User08
User09
$ignoredUsers
Name
----
User05
User10
Administrators
Domain Admins
As for how you're using the .Remove(..) method, since it doesn't produce any output, by doing $users = $users.Remove($item) you're actually setting the variable to null:
PS /> [System.Collections.ArrayList]$arr = 'one','two','three'
PS /> $arr = $arr.Remove('one')
PS /> $null -eq $arr
True
If you want to remove one from the list you would simply do $arr.Remove('one').

Check and Update multiple attributes of AD users

I am trying to do an update to Active Directory from a CSV.
I want to check each value to see if the AD and CSV values match.
If the AD value and CSV values don't match, then I want to update the AD value.
finally I want to create a log of the values changed, which would eventually be exported to a CSV report.
Now there is about 30 values I want to check.
I could do an if statement for each value, but that seems like the hard way to do it.
I am try to use a function, but I cant seem to get it working.
I am getting errors like:
set-ADUser : replace
At line:94 char:9
+ set-ADUser -identity $ADUser -replace #{$ADValue = $DIAccount ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (JDoe:ADUser) [Set-ADUser], ADInvalidOperationException
+ FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.SetADUser
set-ADUser : The specified directory service attribute or value does not exist
Parameter name: Surname
At line:94 char:9
+ set-ADUser -identity $ADUser -replace #{$ADValue = $DIAccount ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (JDoe:ADUser) [Set-ADUser], ArgumentException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft.ActiveDirectory.Management.Commands.SetADUser
Any suggestions would be welcome
Code I am using:
Function AD-Check ($ADValue, $ADUser, $ADAccount, $UpdateAccount)
{
If ($ADAccount -ne $UpdateAccount)
{
set-ADUser -identity $ADUser -replace #{$ADValue = $UpdateAccount}
$Change = "Updated"
}
Else
{
$Change = "No Change"
}
Return $Change
}
$Import = get-content C:\temp\ADUpdates.csv
Foreach ($user in $Import)
{
$Account = get-aduser $User.Samaccountname -Properties *
#First Name Check
$Test = AD-Check "GivenName" $Account.samaccountname $Account.givenname $user.givenname
$ChangeGivenName = $Test
#Initials Check
$Test = AD-Check "Initials" $Account.samaccountname $Account.Initials $user.Initials
$ChangeInitials = $Test
#Last Name Check
$Test = AD-Check "Surname" $Account.samaccountname $Account.SurnameSurname $user.Surname
$ChangeSurname = $Test
}
Reply to Theo, cant seem to add this any other way...
Thanks Theo, it seems to make sense, but getting an error.
Select-Object : Cannot convert System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection to one of the following types {System.String,
System.Management.Automation.ScriptBlock}.
changed the following to get all properties for testing and it works.
$Account = Get-ADUser -Filter "SamAccountName -eq '$sam'" -ErrorAction SilentlyContinue -Properties $propsToCheck
Left the following and it kicks the error
$oldProperties = $Account | Select-Object $propsToCheck
Using the following just for testing:
$propertiesMap = [ordered]#{
SamAccountName = 'sAMAccountName'
mail = 'mail'
GivenName = 'givenName'
Initials = 'initials'
Surname = 'sn'
Office = 'physicalDeliveryOfficeName'
MobilePhone = 'mobile'
DistinguishedName = 'DistinguishedName'
}
Starting of with a WARNING:
Replacing user attributes is not something to be taken lightly and you
need to check any code that does that on a set of testusers first.
Keep the -WhatIf switch to the Set-ADUser cmdlet so you
can first run this without causing any problems to the AD.
Only once you are satisfied all goes according to plan, remove the -WhatIf switch.
Please carefully read all inline comments in the code.
In your code you use an input CSV file, apparently with properties and values to be checked/updated, but instead of using Import-Csv, you do a Get-Content on it, so you'll end up with just lines of text, not an array of parsed properties and values..
Next, as Mathias already commented, you need to use the LDAP attribute names when using either the -Add, -Remove, -Replace, or -Clear parameters of the Set-ADUser cmdlet.
To do what you intend to do, I would first create a hashtable to map the PowerShell attribute names to their LDAP equivalents.
To see which property name maps to what LDAP name, you can use the table here
# create a Hashtable to map the properties you want checked/updated
# the Keys are the PowerShell property names as they should appear in the CSV
# the Values are the LDAP AD attribute names in correct casing.
$propertiesMap = [ordered]#{
SamAccountName = 'sAMAccountName'
GivenName = 'givenName'
Initials = 'initials'
Surname = 'sn'
Office = 'physicalDeliveryOfficeName'
Organization = 'o'
MobilePhone = 'mobile'
# etcetera
}
# for convenience, store the properties in a string array
$propsToCheck = $propertiesMap.Keys | ForEach-Object { $_.ToString() }
# import your CSV file that has all the properties you need checked/updated
$Import = Import-Csv -Path 'C:\temp\ADUpdates.csv'
# loop through all items in the CSV and collect the outputted old and new values in variable $result
$result = foreach ($user in $Import) {
$sam = $user.SamAccountName
# try and find the user by its SamAccountName and retrieve the properties you really want (not ALL)
$Account = Get-ADUser -Filter "SamAccountName -eq '$sam'" -ErrorAction SilentlyContinue -Properties $propsToCheck
if (!$Account) {
Write-Warning "A user with SamAccountName '$sam' does not exist"
continue # skip this one and proceed with the next user from the CSV
}
# keep an object with the current account properties for later logging
$oldProperties = $Account | Select-Object $propsToCheck
# test all the properties and create a Hashtable for the ones that need changing
$replaceHash = #{}
foreach ($prop in $propsToCheck) {
if ($Account.$prop -ne $user.$prop) {
$ldapAttribute = $propertiesMap[$prop] # get the LDAP name from the $propertiesMap Hash
# If any of the properties have a null or empty value Set-ADUser will return an error.
if (![string]::IsNullOrWhiteSpace($($user.$prop))) {
$replaceHash[$ldapAttribute] = $user.$prop
}
else {
Write-Warning "Cannot use '-Replace' with empty value for property '$prop'"
}
}
}
if ($replaceHash.Count -eq 0) {
Write-Host "User '$sam' does not need updating"
continue # skip this one and proceed with the next user from the CSV
}
# try and do the replacements
try {
##########################################################################################################
# for safety, I have added a `-WhatIf` switch, so this wll only show what would happen if the cmdlet runs.
# No real action is performed when using '-WhatIf'
# Obviously, there won't be any difference between the 'OLD_' and 'NEW_' values then
##########################################################################################################
$Account | Set-ADUser -Replace $replaceHash -WhatIf
# refresh the account data
$Account = Get-ADUser -Identity $Account.DistinguishedName -Properties $propsToCheck
$newProperties = $Account | Select-Object $propsToCheck
# create a Hashtable with the old and new values for log output
$changes = [ordered]#{}
foreach ($prop in $propsToCheck) {
$changes["OLD_$property"] = $oldProperties.$prop
$changes["NEW_$property"] = $newProperties.$prop
}
# output this as object to be collected in variable $result
[PsCustomObject]$changes
}
catch {
Write-Warning "Error changing properties on user '$sam':`r`n$($_.Exception.Message)"
}
}
# save the result as CSV file so you can open with Excel
$result | Export-Csv -Path 'C:\temp\ADUpdates_Result.csv' -UseCulture -NoTypeInformation

Powershell & Get-ADUser - Split OU getting the 3rd, 4th and 5th elements from the end

Running the following in Powershell I get the result below.
Get-ADUser -Filter * -Properties DistinguishedName | select DistinguishedName | Select-Object -Property #{label='Result';expression={$_.DistinguishedName}}
Result
CN=User Full Name 1,OU=AMS,OU=DS,DC=domain,DC=local
CN=User Full Name 2,OU=TECHM,OU=DS,DC=domain,DC=local
CN=User Full Name 3,OU=Agencies,OU=HK,OU=Developers,DC=domain,DC=local
CN=User Full Name 4,OU=Agencies,OU=CH,OU=Developers,DC=domain,DC=local
CN=User Full Name 5,OU=Agencies,OU=US,OU=Developers,DC=domain,DC=local
CN=User Full Name 6,OU=Market,OU=PP,OU=Developers,DC=domain,DC=local
CN=User Full Name 7,OU=Market,OU=HK,OU=Developers,DC=domain,DC=local
This is my OU in Active Directory
DS
AMS
TECHM
Developers
CH
Agencies
Market
HK
Agencies
Market
I need a way to adapt this query (doing a split or whatever) so I am able to obtain the third item (comma separated) - most important - from the end and 4th and 5th. This is the output I'd like to get:
OU Where Type
--------------------------------
DS AMS N/A
DS TECHM N/A
Developers CH Agencies
Developers CH Market
Developers HK Agencies
Developers HK Market
I found some examples to split strings but nothing to accomplish what I am trying here.
Any ideas?!
Any help is greatly appreciated. Thanks in advance!
Given the values you're wanting to extract, I'd start with CanonicalName, rather than DistinguishedName:
Get-ADUser -Filter * -Properties CanonicalName |
select -ExpandProperty CanonicalName |
ForEach-Object {
$Parts = $_.split('/')
$Object =
[PSCustomObject]#{
OU = $Parts[1]
Where = $Parts[2]
Type = 'N/A'
}
if ($Parts.count -ge 5)
{ $Object.Type = $Parts[3] }
$Object
}
You are not interested in ",DC=domain,DC=local", so you might as well remove them by either -replace ",DC=domain,DC=local","" or by using substring method of string.
You really did not need that last select-object, nor specifying DistinguishedName, as by default you would get that with get-aduser
For example
$users = (get-aduser -filter *).distinguishedname -replace ",DC=domain,DC=local",""
Then, you split the results with "," to a temporary array variable and get the ones you like, starting from the last one using array index [-1]
$users | foreach {$where = $type = $OU = ""}{
$temp=#($_ -split ",")
if ($temp.count -eq 1) {
$OU = $temp[0].substring(3) # substring to remove the first 3 chars, that is OU= or CN=
} elseif ($temp.count -eq 2) {
$where = $temp[1].substring(3)
$OU = $temp[0].substring(3)
} else {
$type = $temp[-1].substring(3)
$where = $temp[-2].substring(3)
$ou = $temp[-3].substring(3)
}
# Finally create a custom obhect out of these
[PSCustomObject]#{
Type=$Type
Where=$where
OU=$OU
}
}
I am typing this on top of my head, you should test but I guess you get the idea..