Powershell - User creation script avoid duplicate username - powershell

I would like to do an Active Directory search using PowerShell to discover if the username I want to create is already in use,. If it is already in use I want the script to add the following number at the and of the user name.
Import-Module ActiveDirectory
$family= Mclaren
$first= Tony
#This part of the script will use the first 5 letters of $family and the first 2 letters of $first and join them together to give the $username of 7 letters
$username = $family.Substring(0, [math]::Min(5, $family.Length)) + $first.Substring(0, [math]::Min(2, $first.Length))
The user name will look like "mclarto" base on that (username
take the 5 first letters of the family name plus 2 charof the firstname)
a seach is done in AD.
If there is no result, "mclarto" will be taken as $username without
any number at the end.
If the search find other users with the same username, the
username should take the following number, in this case it would be
"mclarto1".
If "mclarto1" already exist then "mclarto2" should be use and so on.

I think this will get you close, it uses the ActiveDirectory module.
Import-Module ActiveDirectory
$family = "Mclaren*"
# Get users matching the search criteria
$MatchingUsers = Get-ADUser -Filter 'UserPrincipalName -like $family'
if ($MatchingUsers)
{
# Get an array of usernames by splitting on the # symbol
$MatchingUsers = $MatchingUsers | Select -expandProperty UserPrincipalName | %{($_ -split "#")[0]}
# loop around each user extracting just the numeric part
$userNumbers = #()
$MatchingUsers | % {
if ($_ -match '\d+')
{
$userNumbers += $matches[0]
}
}
# Find the maximum number
$maxUserNumber = ($userNumbers | Measure-Object -max).Maximum
# Store the result adding one along the way (probably worth double checking it doesn't exist)
$suggestedUserName = $family$($maxUserNumber+1)
}
else
{
# no matches so just use the name
$suggestedUserName = $family
}
# Display the results
Write-Host $suggestedUserName

Related

Powershell to list free domain computers

I'm using PowerShell to get a lit of all our domain computers, and then look for ones that are skipped, so we can easily get free computer names within this name range.
For example:
Get-ADComputer -Filter {Name -like "PC016*"} | Sort-Object | select Name
This gets me all our PCs starting with "PC016", and it works. From here I want to list all the skipped names.
For example, if I have this output:
PC016225
PC016226
PC016228
PC016229
I want Powershell to list the skipped item (PC016227).
How can I do this?
Remove the prefix from the names so you're left with the numerical suffix (eg. 225), then sort the resulting values to easily locate the lowest and highest values, and then simply output any number in between that isn't in the original list:
# Define computer name prefix
$prefix = "PC016"
# Create regex pattern for the prefix + a pattern to match only computer names ending with numbers
$prefixPattern = "^$([regex]::Escape($prefix))"
$namePattern = "${prefixPattern}\d+$"
# Query the directory for computer objects with the given naming prefix,
# filter the result so we only store computers with a numerical suffix
$computerNames = Get-ADComputer -Filter "Name -like '${prefix}*'" |Where-Object Name -match $namePattern |ForEach-Object Name
# Extract the numerical suffix and convert to a numeric type
$values = #($computerNames -replace $prefixPattern) -as [int[]]
# Sort the values
$values = $values |Sort-Object
# Now generate the range of values from the smallest to the biggest,
# filter out any values that are already found in the existing list
$skipped = $values[0]..$values[-1] |Where-Object { $_ -notin $values }
# ... and finally attach the prefix again and output the new possible names
$skipped |ForEach-Object { -join $prefix,$_ }

How to remove impossible to remove white spaces in powershell

Working on a PowerShell script to basically check current available 365 Licenses and pick one to assign to a new user (I know some of it's kinda jank but it's in progress).
It outputs the data collected by Get-MsolAccountSku to an array and then selects which license by index and joins it to the tenant ID from:
(Get-MsolDomain | Where-Object {$_.isInitial}).name
But no matter what I do to remove the whitespace i.e.: using trim, using replace, using regex replaces it always keeps a space around the Name of the License so it shows like:
Redactedtenantname: FLOW_FREE
Instead of:
Redactedtenantname:FLOW_FREE
I did also look at trying to either use -f somehow to format it but I couldn't figure out how to get it to play nice.
The other idea that I think could potentially work is to export it to CSV temporarily and import from CSV to see if getting the credential from CSV would clear any weird whitespace / format.
Just wondering if anyone's run into anything similar/might know what to do.
I've altered some of the stuff here to get it to fit so if the formatting of the output is a bit weird (short of the whitespace) it might just be how I copied it across.
Also if it helps the final $sku variable I get is a System.String
#Get Tenancy name (For license assignment)
$Tenant = (Get-MsolDomain | Where-Object {$_.isInitial}).name
#Get list of licenses
$test = Get-MsolAccountSku | select -ExpandProperty AccountSkuID
#Read host for selection of license (at this time only does 1 at a time will improve on)
$selection = Read-Host "Pick an object on the list i.e 1 for the first item 2 for the second etc"
#the array starts at 0 so just subtracting from the selection to make it 1-1 i.e 1 equals one 2 equals 2 etc.
$select = $selection - 1
#this selects an object from the array based on the number selected and altered above
$license = $test | Select-Object -index $select
#Manipulating the data selected above to make it fit our needs below Splitting at the : and replacing uneccessary data
$365license = #($license -split ':')
$3651 = $365license -replace '(^\s+|\s+$)','' -replace '\s+',''
$3652 = $361 -replace "redactedtenantsname",""
$tenant1 = $tenant -replace ".onmicrosft.com",""
#Joining tenancy name to formatted license name
$presku1 = "$tenant1",":","$3652"
-join $presku1
$sku = "$presku1"
$upn = "redactedtestuserupn"
Set-msoluserlicense -userprincipalname "$upn" -addlicenses "$sku"
It looks to me like the core of your problem is that you've got a string in the format <account>:<skuId> and you want to replace the <account> part with something else.
# equivalent to
# $Tenant = (Get-MsolDomain | Where-Object {$_.isInitial}).name
$tenantDomain = "my_tenant_name.onmicrosoft.com";
$tenantName = $tenantDomain -replace ".onmicrosoft.com", "";
# e.g. "my_tenant_name"
# equivalent to
# $test = Get-MsolAccountSku | select -ExpandProperty AccountSkuID
$accountSkuIds = #(
# the unique string ID of the account/SKU combination.
# see https://learn.microsoft.com/en-us/powershell/module/msonline/get-msolaccountsku?view=azureadps-1.0#outputs
"my_other_tenant:ENTERPRISEPACK"
"my_other_tenant:AAD_BASIC"
"my_other_tenant:FLOW_FREE"
"my_other_tenant:POWER_BI_STANDARD"
);
# ask the user which item they want to use
$selectedIndex = 3;
# get the selected sku from the array, remembering that arrays are
# zero-based we we need to subtract 1 from the user's selection
$accountSkuId = $accountSkuIds[$selectedIndex - 1];
# e.g. "my_other_tenant:FLOW_FREE"
# split the string "<account>:<sku>" into the array #( "<account>", "<sku>" )
# and then take the *second* item (i.e. [1])
$skuId = #( $accountSkuId -split ":" )[1];
# e.g. "FLOW_FREE"
# convert the #( "<new_account>", "<sku>" ) into the string "<new_account>:<sku>"
$license = #( $tenantName, $skuId ) -join ":";
# e.g. "my_tenant_name:FLOW_FREE"
# call the api
$upn = "my_upn";
Set-MsolUserLicense -UserPrincipalName $upn -AddLicenses $license;

Add 'Next Available AD Computer' to power-shell script

Currently, I'm trying to add a function to my powershell script with the following goal:
On a computer that isn't added to the domain (yet), have it search a local AD server (Not azure) for the next available name based off the user's input.
I have tried and failed to use arrays in the past, and I want to use the Get-ADComputer cmdlet in this, but I'm not sure how to implement it.
$usrinput = Read-Host 'The current PC name is $pcname , would you like to rename it? (Y/N)'
if($usrinput -like "*Y*") {
Write-Output ""
$global:pcname = Read-Host "Please enter the desired PC Name"
Write-Output ""
$userinput = Read-Host "You've entered $pcname, is this correct? (Y/N)"
if($usrinput -like "*N*") {
GenName
#name of the parent function
}
Write-Output ""
The above code is part of a larger script that parses a computer name and assigns it to the correct OU in the end.
Our naming scheme works like this: BTS-ONE-LAP-000
So it is: Department - Location - Device Type - Device Count
The code will then take the first part "BTS-ONE" and parse it for the correct OU it should go to, and then assign it using the Add-Computer cmdlet. It will also rename the machine to whatever the user typed in ($pcname).
So, before it parses the name, I'd like it to search all current names in AD.
So, the user can type in: "BTS-ONE-LAP" and it will automatically find the next available Device Count, and add it to the name. So, it will automatically generate "BTS-ONE-LAP-041".
Added Note:
I've used Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"' | FT Name and the output is
Name
----
BTS-ONE-LAP-001
BTS-ONE-LAP-002
BTS-ONE-LAP-006
BTS-ONE-LAP-007
BTS-ONE-LAP-009
BTS-ONE-LAP-010
BTS-ONE-LAP-022
BTS-ONE-LAP-024
BTS-ONE-LAP-025
BTS-ONE-LAP-028
BTS-ONE-LAP-029
BTS-ONE-LAP-030
BTS-ONE-LAP-031
BTS-ONE-LAP-032
BTS-ONE-LAP-034
BTS-ONE-LAP-035
BTS-ONE-LAP-036
BTS-ONE-LAP-037
BTS-ONE-LAP-038
BTS-ONE-LAP-039
BTS-ONE-LAP-040
BTS-ONE-LAP-041
BTS-ONE-LAP-050
BTS-ONE-LAP-051
I don't know how to parse this so the code knows that BTS-ONE-LAP-003 is available (I'm terrible with arrays).
$list = (Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"' | Sort-Object Name[-1])
$i = 1
$found = $false
Foreach($Name in $list.Name)
{
while($i -eq [int]$Name.Split("-")[3].Split("-")[0])
{
$i++
}
}
$i
The above code will go through each name in the list, and will stop when it discovers say the 3rd computer in the set is NOT computer #3.
Example:
BTS-ONE-LAP-001 | $i = 1
BTS-ONE-LAP-002 | $i = 2
BTS-ONE-LAP-006 | $i = 3
It split BTS-ONE-LAP-006 to be 006, and convert it to an integer, making it 6.
Since 6 does not equal 3, we know that BTS-ONE-LAP-003 is available.
Another way could be to create a reusable function like below:
function Find-FirstAvailableNumber ([int[]]$Numbers, [int]$Start = 1) {
$Numbers | Sort-Object -Unique | ForEach-Object {
if ($Start -ne $_) { return $Start }
$Start++
}
# no gap found, return the next highest value
return $Start
}
# create an array of integer values taken from the computer names
# and use the helper function to find the first available number
$numbers = (Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"') |
ForEach-Object { [int](([regex]'(\d+$)').Match($_.Name).Groups[1].Value) }
# find the first available number or the next highest if there was no gap
$newNumber = Find-FirstAvailableNumber $numbers
# create the new computername using that number, formatted with leading zero's
$newComputerName = 'BTS-ONE-LAP-{0:000}' -f $newNumber
Using your example list, $newComputerName would become BTS-ONE-LAP-003
Note that not everything a user might type in with Read-Host is a valid computer name. You should add some checks to see if the proposed name is acceptable or skip the proposed name alltogehter, since all your machines are named 'BTS-ONE-LAP-XXX'.
See Naming conventions in Active Directory for computers, domains, sites, and OUs

Powershell AD-Object DistinguishedName attributes

I am trying to check if an object returned by the Get-Adcomputer belongs to a certain group under the DistinguishedName property returned. I can't seem to find anything online and looking for some pointers. This attribute contains several different things and I am interested in the OU value in particular.
$computer = Get-AdComputer "$computerName"
$computer.DistinguishedName
This returns several attributes linked to OU and CN, but I am just interested if one of the OU attributes matches 'Server'.
here is something you could try if you're just looking for a true/false check
$computer = Get-AdComputer "$computerName"
$dn = $computer.DistinguishedName
$dn.Split(',') -match '^ou=.*server'
or you could use this to get the whole path
# split the distinguishedname into an array of CNs, OUs, etc.
$split = $dn.Split(',')
# setup match variable to signal that we found an object that matches what we are looking for.
$match = $false
# for each item in the array
($split | % {
# only check this if we have not yet found a match
# if this line is an OU whose name contains the word 'server'
if (!$match -and $_ -match '^ou=.*server') {
# set match to true because we found what we were looking for
$match = $true
}
# if we have found a match, output this and all the rest of the objects to the pipeline
if ($match) {
$_
}
# now we have an array starting at the OU we were looking for
# this join will put the pieces back together into a string so it looks right
}) -join ','
note: this will likely not work if any of your objects have a comma in the name.

Using Powershell to Loop check usernames from csv, loop through AD and add a number if needed

Here is my scenario,
I wrote a SQL query that extracts user accounts with a null username from our student information system. Lets just assume these are newly enrolled students. I then want to take this list, which has a column of suggested usernames, done by simple concatenation in the SQL query. I want to loop through that csv and check to make sure that username doesn't already exist in AD , if it does, append the next available number to the username.
So in my test environment I have a csv that looks like this. ( I made this up for testing)
StudentID,First,Last,SuggestedUsername
12345,tony,Test,testto
54321,tolly,test,testto
I my test AD environment I already have a student named Tommy Test or Testto, so in this case, my powershell script should tell me Tony Test should be testto1 and Tolly Test should be testto2. Is this making sense?
The meat of my script works, It will read the csv, loop through AD and return testto1 for line 1 of the csv, the problem is it will not read line 2, the script ends
I have been playing around with the arrays in the script but here is what I have so far
Import-module Activedirectory
Add-Pssnapin Quest.ActiveRoles.admanagement
$useraccounts =#()
$useraccounts = import-Csv "Path\Test.csv"
$userbase = Get-QADuser -sizelimit 0 -SearchRoot 'mydomain.com/OU'
foreach ($user in $useraccounts) {
if ($userbase)
{
$userbase = $userbase | Select samaccountname | %{($_ -split "#")[0]}
$UserNumbers = #()
$userbase | % {
if ($_ -Match '\d+')
{
$UserNumbers += $matches[0]
}
}
$MaxUserNumber = ($userNumbers | Measure-Object -max).Maximum
$suggestedUserName = $user+($MaxUserNumber+1)
}
Else
{
$SuggestedUserName = $user
}
}
Write-Host $suggestedUserName
Ok, your loop doesn't appear to be cycling because you aren't using an array of strings as your output, you are just using a string, so it just shows up with the last loop. But if you like my solution that's neither here nor there, because I think I have a better option for you. Just for the sake of simplicity, back up your CSV and then delete the Suggested Name column and run this against that.
$Users = Import-CSV "path\test.csv"
$Users|%{$_|Add-Member UserID ("$($_.last)$($_.first.substring(0,2))")}
$NameConflicts = $Users|group userid|?{$_.count -gt 1}
ForEach($Name in $NameConflicts){
$x=0
if(dsquery user -samid "$($name.name)*"){
$x = Get-ADUser -Filter {samaccountname -like "$($Name.Name)*"}|%{$_.samaccountname -replace "($($Name.name))(\d*)","`$2"}|sort -Descending |select -first 1
}
For($i=if($x -gt 0){0}else{1};$i -lt ($Name.count);$i++){
$Name.Group[$i].UserID = "$($Name.Name)$($i+$x)"
}
}
$Users|group userid|?{$_.count -eq 1}|%{if(dsquery user -samid "$($_.name)"){$_.Group[0].UserID = "$($_.Name)1"}}
$Users|FT -auto
That loads your list, creates potential user names by taking the last name and the first two letters of the first name. Then it groups by that, and finds any of them that have more than one. Then for each of those it checks AD for any existing accounts with names like that, and takes the number off the end of the name, and selects the highest one. Then if it found any already in existence it renames all of the potential user names to append the next available number to the end (if none are found in AD it leaves the first one, and renames the rest of them). Lastly it checks all of the other names, where there is just one user name, and if it is in AD already it adds a 1 to the end of the user id.
This should run faster than your script, because I'm only searching for names that are needed to be checked, and it's filtering at the provider level instead of taking all names, and then filtering through powershell.