Need guidance on String Manipulation

I’ve got the following function snippet that I’m looking for a little guidance on. I need to check that my var isn’t already in use as an active account. If the var is in use I need to modify it as you can see in my commented lines below. By the time $SamAcctName makes it to this function I’ve already “cleaned it up” (removed spaces, apostrophes, dashes, maxlength, etc.). I’d like to either strip the last two characters of the var and replace them with numerals, or append the numerals to the current var. I know the answer is in string processing but what string methods should I be focusing on? Also I need to check if john.smith or ANY john.smithXX exists so I know what my next number will be. What logic construct(s) should I be looking at as well? THANK YOU in advance!

Function Check-SamAcct {
    $CheckSam = (Get-ADUser $SamAcctName).SamAccountName
    If ($CheckSam -eq $SamAcctName) {
	
	# Code to modify $SamAcctName goes here
	# I need to take the current var and modify/create
	# a new one if that samAccountName already exists.
	# I need this function to return a new unique var.
	# i.e. $SamAcctName = "john.smith" and a john.smith 
	# already exists.  I need to create john.smith01 and
	# assign that to $SamAcctName so I can create the new
	# user account.  Make sense?

        }

I think this is basically what you are trying to do. There are a lot of ways to do this, but I think Compare-Object is what you should use. Basically, you would generate a object with the possible names and then run a query against AD with a wildcard. Then compare those two object to determine what is available and select the first item. You just need to change the $SamAccountNameFormat to Firstname.LastName, un-remark the AD search line and remove the mock AD data lines:

function Get-UniqueSamAccountName {
    param[
        [string]$FirstName,
        [string]$LastName
    ]

    begin{
        $SamAccountNameFormat = "{0}{1}" -f $FirstName.SubString[0,1], $LastName
        #Generate a list of the possible names, currently up to 50
        $possibleNames = @{}
        $possibleNames += New-Object -TypeName PSObject -Property @{SamAccountName=$SamAccountNameFormat}
        $possibleNames += for[$i=1;$i -le 50;$i++]{$name = "{0}{1:D2}" -f $SamAccountNameFormat, $i; New-Object -TypeName PSObject -Property @{SamAccountName=$name}}
        #Create a search filter for Get-ADUser with a * wildcard
        $searchFilter = "{0}*" -f $SamAccountNameFormat
    }
    process {
        #$currentlyInAD = Get-ADUser -Filter {SamAccountName -Like $searchFilter} -Properties SamAccountName | Select SamAccountName
 
        # Don't have AD handy, but emulate a return from AD
        $currentlyInAD = @{}
        $currentlyInAD += New-Object -TypeName PSObject -Property @{SamAccountName="rsimmers"}
        $currentlyInAD += New-Object -TypeName PSObject -Property @{SamAccountName="rsimmers01"}
        $currentlyInAD += New-Object -TypeName PSObject -Property @{SamAccountName="rsimmers02"}
        $currentlyInAD += New-Object -TypeName PSObject -Property @{SamAccountName="rsimmers04"}
        $currentlyInAD += New-Object -TypeName PSObject -Property @{SamAccountName="rsimmers05"}
        # Compare the list possibilities with the returned AD accounts for the SamAccountName format
        $availableSAM = Compare-Object -ReferenceObject $currentlyInAD -DifferenceObject $possibleNames -Property SamAccountName -PassThru | Where{$_.SideIndicator -eq "=>"} | Sort-Object SamAccountName | Select -ExpandProperty SamAccountName -First 1
    }
    end {
        $availableSAM
        
    }
}

Get-UniqueSamAccountName -FirstName Rob -LastName Simmers

Return:

Get-UniqueSamAccountName -FirstName Rob -LastName Simmers
RSimmers03

You can also remark out the first line of the mocked AD data for “rsimmers” or “rsimmers01” and see that the function will return the next logical samaccountname.

Rob,
THANK YOU for the info it definitely gives me a different perspective! I’ve already passed the var $SamAcctName to the Function and I’d like to use what I already have since $SamAcctName has already been pre-processed/cleaned it up.

$SamAcctName = ‘alexander.richardson’

The example above is a 20 Character string and the max for a SamAccountName. I need to drop the 2 rightmost characters:
$SamAcctName.Substring(0,$SamAcctName.Length -2) and somehow append two numerals i.e. 01 02 03

For a $SamAcctName that is < 18 I need to append two numerals to that.

My biggest hurdle at this point is the logic/construct needed to check if john.smith01, john.smith02, john.smith03 exists and iterate from there. I get the gist of your code but was hoping for something a bit more streamlined for my function. I'll definitely digest it and see if I can incorporate some of it. THANK YOU!

Edited to fix butchered code block

Script:

$Names = 'alexander.richardson','alexander.richards02','alexander.richards03','alexander.richards04'
$SAM = 'alexander.richardson'
$SuffixArray = '02','03','04','05','06','07','08','09','10'
$i = 0

while ($Names -contains $SAM){
    "Name:  $SAM"
    "Oh no, $SAM already in use!"
    if ($SuffixArray -contains $SAM.Substring($SAM.Length-2,2)){
        "  It already has a suffix"
        "  Increment the suffix and try again!"
        $SAM = $SAM.Substring(0,$SAM.Length-2) + $SuffixArray[$i]
    } else {
        "  Doesn't contain a suffix yet"
        "  Is the name too long?"
        if ($SAM.Length -gt 18){
            "  Yep, shrink it down and increment"
            $SAM = $SAM.Substring(0,18) + $SuffixArray[$i]
        } else {
            "  Nope it's good, increment it"
            $SAM = $SAM + $SuffixArray[$i]
        }
    }
    $i++
    "==================="
}

"Boomshockalocka: $SAM"

Output:

Name:  alexander.richardson
Oh no, alexander.richardson already in use!
  Doesn't contain a suffix yet
  Is it too long?
  Yep, shrink it down and increment
===================
Name:  alexander.richards02
Oh no, alexander.richards02 already in use!
  It already has a suffix
  Increment the suffix and try again!
===================
Name:  alexander.richards03
Oh no, alexander.richards03 already in use!
  It already has a suffix
  Increment the suffix and try again!
===================
Name:  alexander.richards04
Oh no, alexander.richards04 already in use!
  It already has a suffix
  Increment the suffix and try again!
===================

Boomshockalocka: alexander.richards05

Don’t have time to test all scenarios there may be bugs but hopefully this gets you stepping in the right direction. Since you want to use “01” or “02” instead of “1” and “2” I used strings instead of integers because I don’t know how to append the preceding zero to an int. The downfall of this is that your increments are finite but add as many suffixes to the array as necessary. Also I started the array at “02” because it makes sense that the first person with a matching name be 02 and not 01.

Obviously you’ll use Get-ADUser as the while loop argument.

Good luck!

I played a little more with this today. Give this a shot:

function Get-UniqueSamAccountName {
    [CmdletBinding()]
    param(
        [string]$FirstName,
        [string]$LastName,
        [int]$MaxSamNames = 30,
        [int]$MaxSamLength = 20
    )
 
    begin{
        #$SamAccountNameFormat = "{0}.{1}" -f $FirstName, $LastName
        $SamAccountNameFormat = "{0}{1}" -f $FirstName.SubString(0,1), $LastName
        Write-Verbose ("SamAccountName will be truncated to {0} characters" -f $MaxSamLength)
        if ($SamAccountNameFormat.Length -gt $MaxSamLength) {$SamAccountNameFormat = $SamAccountNameFormat.SubString(0,$MaxSamLength)}
        Write-Verbose ("SamAccountName Format: {0}" -f $SamAccountNameFormat)
        Write-Verbose ("Compiling object with {0} SamAccountNames for comparison" -f ($MaxSamNames + 1))
        $possibleNames = @()
        $possibleNames += New-Object -TypeName PSObject -Property @{SamAccountName=$SamAccountNameFormat}
        if ($SamAccountNameFormat.Length -gt ($MaxSamLength -2)) {$SamAccountNameFormat = $SamAccountNameFormat.SubString(0,($MaxSamLength -2))}
        $possibleNames += for($i=1;$i -le $MaxSamNames;$i++){$name = "{0}{1:D2}" -f $SamAccountNameFormat, $i; New-Object -TypeName PSObject -Property @{SamAccountName=$name}}
        #Create a search filter for Get-ADUser with a * wildcard
        $searchFilter = "{0}*" -f $SamAccountNameFormat
    }
    process {
        #Get any user that matches the searchFilter
        Write-Verbose ("Searching Active Directory for user(s): {0}" -f $searchFilter)
        $currentlyInAD = Get-ADUser -Filter {SamAccountName -Like $searchFilter} -Properties SamAccountName | Select SamAccountName
 
        if ($currentlyInAD) {
            Write-Verbose ("Found {0} matches in AD, comparing with possible names list for available SamAccountName" -f @($currentlyInAD).Count)
            # Compare the list possibilities with the returned AD accounts for the SamAccountName format
            $availableSAM = Compare-Object -ReferenceObject $currentlyInAD -DifferenceObject $possibleNames -Property SamAccountName -PassThru | Where{$_.SideIndicator -eq "=>"} | Select -ExpandProperty SamAccountName -First 1
        }
        else {
            Write-Verbose "No matches found in AD, returning first possible SamAccountName"
            #$possibleNames | Sort-Object -Property SamAccountName
            $possibleNames | Select -ExpandProperty SamAccountName -First 1
        }
    }
    end {
        $availableSAM
    }
}

Some sample output:

Get-UniqueSamAccountName -FirstName Rob -LastName Simmers -Verbose
Get-UniqueSamAccountName -FirstName Bill -LastName Frazier -Verbose
Get-UniqueSamAccountName -FirstName Donald -LastName Rumpelstiltskin  -Verbose
Get-UniqueSamAccountName -FirstName Alexander -LastName Richardson  -Verbose
Get-UniqueSamAccountName -FirstName Donald -LastName Rumpelstiltskin -MaxSamLength 8 -Verbose
Get-UniqueSamAccountName -FirstName Alexander -LastName Richardson  -MaxSamLength 8 -Verbose

VERBOSE: SamAccountName will be truncated to 20 characters
VERBOSE: SamAccountName Format: RSimmers
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): RSimmers*
VERBOSE: Found 1 matches in AD, comparing with possible names list for available SamAccountName
RSimmers01
VERBOSE: SamAccountName will be truncated to 20 characters
VERBOSE: SamAccountName Format: BFrazier
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): BFrazier*
VERBOSE: No matches found in AD, returning first possible SamAccountName
BFrazier
VERBOSE: SamAccountName will be truncated to 20 characters
VERBOSE: SamAccountName Format: DRumpelstiltskin
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): DRumpelstiltskin*
VERBOSE: No matches found in AD, returning first possible SamAccountName
DRumpelstiltskin
VERBOSE: SamAccountName will be truncated to 20 characters
VERBOSE: SamAccountName Format: ARichardson
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): ARichardson*
VERBOSE: No matches found in AD, returning first possible SamAccountName
ARichardson
VERBOSE: SamAccountName will be truncated to 8 characters
VERBOSE: SamAccountName Format: DRumpels
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): DRumpe*
VERBOSE: No matches found in AD, returning first possible SamAccountName
DRumpels
VERBOSE: SamAccountName will be truncated to 8 characters
VERBOSE: SamAccountName Format: ARichard
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): ARicha*
VERBOSE: No matches found in AD, returning first possible SamAccountName
ARichard

or you can un-remark the firstname.lastname format and get something like this:

VERBOSE: SamAccountName will be truncated to 20 characters
VERBOSE: SamAccountName Format: Rob.Simmers
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): Rob.Simmers*
VERBOSE: No matches found in AD, returning first possible SamAccountName
Rob.Simmers
VERBOSE: SamAccountName will be truncated to 20 characters
VERBOSE: SamAccountName Format: Bill.Frazier
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): Bill.Frazier*
VERBOSE: No matches found in AD, returning first possible SamAccountName
Bill.Frazier
VERBOSE: SamAccountName will be truncated to 20 characters
VERBOSE: SamAccountName Format: Donald.Rumpelstiltsk
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): Donald.Rumpelstilt*
VERBOSE: No matches found in AD, returning first possible SamAccountName
Donald.Rumpelstiltsk
VERBOSE: SamAccountName will be truncated to 20 characters
VERBOSE: SamAccountName Format: Alexander.Richardson
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): Alexander.Richards*
VERBOSE: No matches found in AD, returning first possible SamAccountName
Alexander.Richardson
VERBOSE: SamAccountName will be truncated to 8 characters
VERBOSE: SamAccountName Format: Donald.R
VERBOSE: Compiling object with 31 SamAccountNames for comparison
VERBOSE: Searching Active Directory for user(s): Donald*
VERBOSE: No matches found in AD, returning first possible SamAccountName

Rob/Jack,
THANK YOU GUYS! You’ve given me some good ideas. I TRULY appreciate the assistance!