Powershell to list free domain computers - powershell

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,$_ }

Related

PowerShell filtering twice on the same property

I would like to filter the content of a variable by two criteria referring to the same property. I ran into some struggle finding the correct syntax.
It looks like this:
$g = $allsites | Where-Object {($_.Name -like "[g]*") -and ($_.Name -notlike "[GRP-]*)"}
What I'm trying to achieve:
Create the variable $g with the filtered content of the variable $allsites where the Site Name starts with with the letter "G" no matter the following letters. This result has to be filtered again. The content of the variable $g should contain site which are not named starting the expression "GRP-" only.
The construct [GRP-] describes a character set, so you're instructing PowerShell to test if the name doesn't start with either G, R, P or -. Since you've already ensured that all names start with g already, this won't match any of them.
Change the pattern to just GRP-*:
$g = $allsites | Where-Object {$_.Name -like "G*" -and $_.Name -notlike "GRP-*"}

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

How to remove characters from an item in an array using PowerShell

I have an array of computer names, $computers, that are in a FQDN format. I want to trim all characters to the right of the first period and including the period.
Ex: server-01.mydomain.int = server-01
This is what I tried but it errors out.
$computers = Get-VM | Select Name
$computers = $computers.Substring(0, $computers.IndexOf('.'))
$computers
When you do |Select Name, PowerShell returns an object with a Name property, rather than only the value of each Name property of the input objects.
You could change it to Select -ExpandProperty Name and then iterate over each item in the array for the actual substring operation using a loop or ForEach-Object - although we can skip the first step completely:
$computers = Get-VM |ForEach-Object { $_.Name.Substring(0, $_.Name.Indexof('.')) }
Or another way.
$computers = Get-VM | ForEach-Object { ($_.Name -split ".")[0] }
Since you are always selecting the first string before the first "." you can just split at the dot and select the first element in the resulting array.

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.

Powershell - User creation script avoid duplicate username

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