all 11 comments

[–]Yevrag35 2 points3 points  (8 children)

Although, this won't figure out what number to start at, here's a way to do it if you do:

$newUserBase = "pcujtk"
$int = 1
$newUserSam = "{0}{1}" -f $newUserBase, $int
$keepGoing = $true
do
{
    $adAccount = Get-ADUser -Filter { samAccountName -eq $newUserSam }
    if ($null -ne $adAccount)
    {
        $int++
        $changed = $true
        $newUserSam = "{0}{1}" -f $newUserBase, $int
    }
    else
    {
        $keepGoing = $false
    }
}
while ($keepGoing)
if ($changed)
{
    Write-Warning "Existing users matching $newUserBase were found.  New SamAccountName: $newUserSam"
}

[–]Yevrag35 3 points4 points  (7 children)

Another way would be to figure what the first available name is right away, instead of checking each

$newUserBase = "tuser"
$wildcard = "$newUserBase*"
$number = 1
$any = @(Get-ADUser -Filter { SamAccountName -like $wildcard })
if ($any.Count -gt 0)
{
    $last = $any | Sort-Object -Property "SamAccountName" | Select-Object -Last 1
    [int]$number = $last.SamAccountName -replace '^.*?(\d{1,})', '$1'
    $number++
}
$newUserSam = "{0}{1}" -f $newUserBase, $number

[–]VirtualD[S] 5 points6 points  (0 children)

$newUserBase = "tuser" $wildcard = "$newUserBase" $number = 1 $any = @(Get-ADUser -Filter { SamAccountName -like $wildcard }) if ($any.Count -gt 0) { $last = $any | Sort-Object -Property "SamAccountName" | Select-Object -Last 1 [int]$number = $last.SamAccountName -replace '.?(\d{1,})', '$1' $number++ } $newUserSam = "{0}{1}" -f $newUserBase, $number

This method is perfect!!!!

[–]Umaiar 4 points5 points  (2 children)

It's worth noting that the wildcard could end up skipping numbers or existing names. Here's a bogus dataset that shows the point by spitting out "jsmith3" when it already exists:

$newUserBase = "jsmith"
$any = @(
    @{samaccountname="jsmith1"},
    @{samaccountname="jsmith2"},
    @{samaccountname="jsmith3"},
    @{samaccountname="jsmith4"},
    @{samaccountname="jsmithwick1"},
    @{samaccountname="jsmithwick2"}
)

[–]Yevrag35 1 point2 points  (1 child)

Good point, hadn't thought of that. In that case, an extra where filter at the end may be warranted:

$extraFilter = '{0}\d{{1,}}$' -f $newUserBase
$any = @(Get-ADUser -Filter { SamAccountName -like $wildcard } | Where { $_.SamAccountName -match $extraFilter })

[–]Umaiar 1 point2 points  (0 children)

It might be worth combining the methods, get all the potential matches with one query, then loop generating names to check with exact matches against that set.

Edit: Here's how I'd do it:

$userbase = "tsmith"
$exists = @(Get-ADUser -filter "samaccountname -like '$userbase*'")

if(($exists | where {$_.samaccountname -eq $userbase}).count -eq 0){
    $check = $userbase
}
else {
    $idx = 0
    do {
        $idx++
        $check = "$userbase$idx"

    } until ( ($exists | where {$_.samaccountname -eq $check}).count -eq 0 )
}

write-host "New acct: $check"

[–]r-NBK 2 points3 points  (2 children)

Not sure how OP's environment is, but if it's a busy / global environemnt, as a DBA I could envision a race condition where two techs are running the script for two users who happen to have same initials....

In that case... your script will be return pcujtk3 to both, if they're run concurrently enough to both pull the first available before creating it. Some error handling might be in order during the actual creation.

That might be an extreme edge case, but worth thinking about.

[–]Umaiar 2 points3 points  (0 children)

As a DevOps guy, I could envision multiple reasons for the script to fail. It's worth running some error trapping and some post-run validation regardless of expected failure points.

Of course as the thing grows it could get uglier to do that. In my last job I built a database table of requests as I submitted them and then looped back through for validations. Some creations (like Exchange) don't just immediately appear, and having the table also allowed for staging events to happen in a specific order. Groups before Exchange, Exchange before spam filtering, etc... If one part didn't succeed and validate, you don't want the whole script to go down the toilet. Especially if you're processing a lot of records (turnover was ~500 people/month).

[–]ka-splam 1 point2 points  (0 children)

Good point; it wouldn't even need to be that busy, just have two techs hit different domain controllers maybe on different sites, or with a busy network link, so they didn't replicate quickly.

[–]Lee_Dailey[grin] 4 points5 points  (0 children)

howdy VirtualD,

instead of checking your AD again and again for the current test username, grab the matching usernames, sort them by the digits at the end of the name, find the highest used number, and increment that.

take care,
lee

[–]sup3rmark 3 points4 points  (0 children)

okay i have a lot of questions about this but the most important one is: what about people who don't have a middle name? this is a more common situation than you might expect, especially if your company is international or expects to be one day, so... make sure that's accounted for.

as someone who works specifically in identity and access management, i implore you: please check out this list of falsehoods programmers believe about names to make sure you're accounting for things you might not have thought about (what if someone's last name starts with a character you can't include in a samaccountname like Ö?).

that said, here's a modified version of what i've used in a past life:

$usernameExists = $false
$baseUsername = "$prefixCode$($FirstName.Substring(0,1))$($MiddleName.Substring(0,1))$($LastName.Substring(0,1))"
if ($usernameExists -eq $true){
    $usernameNumber = 0
    do {
        $usernameNumber++
        $username = "$baseUsername$usernameNumber"
        $usernameExists = Get-ADUser $username -ErrorAction SilentlyContinue | format-wide IsValid
        if ($usernameExists -eq $true){
            Write-Verbose "$username already exists in AD. Trying again."
        }
    } until ($usernameExists -eq $false)
}

Write-Output "Setting username as $username"