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

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

Related

PowerShell Script to implement ipsec rule

I am troubleshooting an issue in our local IT infrastructure. Some time ago a GPO was pushed that blocked traffic from our IT administration program to our production devices.
Long story short the big company made a decision which wrongly affects our very specific IT needs/design in our department.
Due to sheer coincidence we managed to resolve the issue by manually adding an IPSEC security exception on a device to solve a different issue.
Now the below dodgy attempt by me to make a PS command is just a base as the correct parameters are still to be decided after meeting with multiple sides of the business and IT.
But to reduce the time I need to implement the solution on hundreds of our devices I would like to get a script working where I just have to add or adjust the parameters when I receive the word "go"
I would need the command below to be useable with an input (list/array) of all our devices. I am looking into the CimSession cmdlet but I struggle to come up with a solution to loop through a list/array and add both the target computer and its IP address to the script.
Thank you in advance for your tips on how to proceed.
With the responses below I have expanded the script to the following:
```Powershell
# Ask for the csv file
$CsvLocation = Read-Host -Prompt 'input the location of the csv file (for
example c:\Users\USERNAME\Documents\workstations.csv)'
$CsvFile = Import-CSV -Path $CsvLocation
# Create empty Hash Table
$Machines = #{Workstation = "Test" ; IP = "123"}
# create a hashtable to store the parameters in for splatting
$ruleParams = #{
Confirm = $false
Enabled = $true
Description = 'This rule is instated to allow MobiControl
Administration to be performed on this device.'
DisplayName = 'MobiControl connection'
IPsecRuleName = 'Mobicontrol connection'
OutboundSecurity = 'None'
InboundSecurity = 'None'
Platform = '6.1+'
PolicyStore = 'PersistentStore'
Profile = 'Any'
RemoteAddress = '10.133.120.207'
RequireAuthorization = $false
Protocol = 'any'
}
# For each Element in the csv file add name and ip address to the hash
table
$CsvFile | ForEach-Object {
$Workstation = $_.Workstation
$IpAddress = [System.Net.Dns]::GetHostAddresses($Workstation) |
Where-Object { $_.AddressFamily -eq 'InterNetwork' } | Select-Object -
ExpandProperty IpAddressToString
$Machines.add($Workstation, $IpAddress)
# fill in the two remaining parameters with the IP address and computer
name
<# test print contents
Read-Host "press enter to see the values for hostname and ip address"
Echo $Machines.keys
Read-Host "press enter to continue"
#>
$ruleParams['LocalAddress'] = $_.Value # IP Address
$ruleParams['CimSession'] = $_.Key # HostName
# execute using the ruleParams splat
Write-Host "Creating IPsecRule on computer $() with IP address $()"
# New-NetIPsecRule #ruleParams
}
This looks to be more in the direction I want. Any obvious flaws?
the input csv file would just be a list of workstation names.
Testing the code all seems to be in working order up until the execution of the New-NetIPsecRule. The values inside the hashtable $Machines are non valid inputs for their related parameters.
The way you add the parameters to the cmdlet is incorrect and would require the much hated backticks at the end of each line, preceeded with a space.
Similar that, but much better is to use Splatting
# create a hashtable to store the parameters in for splatting
$ruleParams = #{
Confirm = $false
Enabled = $true
Description = 'This rule is instated to allow MobiControl Administration to be performed on this device.'
DisplayName = 'MobiControl connection'
IPsecRuleName = 'Mobicontrol connection'
OutboundSecurity = 'None'
InboundSecurity = 'None'
Platform = '6.1+'
PolicyStore = 'PersistentStore'
Profile = 'Any'
RemoteAddress = '10.133.120.207'
RequireAuthorization = $false
# I'm not sure about the Protocol parameter..
# The docs say it is a String, but also refer to the Get-NetFirewallPortFilter
# cmdlet where this parameter is a string array (String[])
Protocol = 'TCP,UDP'
}
# now iterate over the $machines hashtable, fill in the two missing parameters in the hash and execute
$machines.GetEnumerator() | ForEach-Object {
$CimSession = Get-CimSession -ComputerName $_.Key
# fill in the two remaining parameters with the IP address and computer name
$ruleParams['LocalAddress'] = $_.Value # IP Address
$ruleParams['CimSession'] = $CimSession
# execute using the ruleParams splat
Write-Host "Creating IPsecRule on computer $($_.Key) with IP address $($_.Value)"
New-NetIPsecRule #ruleParams
$CimSession | Remove-CimSession
}
Disclaimer.. I cannot try this myself, and as I'm not sure the Protocol parameter should be a single comma separated string or a string array, please try this on a limited test set of machines first.
P.S. When creating the $machines hashtable, change this line
$IpAddress = [System.Net.Dns]::GetHostAddresses($Workstation) |
Where-Object { $_.AddressFamily -eq 'InterNetwork' } | select IpAddressToString
into
$IpAddress = [System.Net.Dns]::GetHostAddresses($Workstation) |
Where-Object { $_.AddressFamily -eq 'InterNetwork' } | Select-Object -ExpandProperty IpAddressToString

How to find the lowest numbered computer on a network

I am attempting to write a script to copy some information from one computer to another. The first computer has a name similar to "SERVERxx" where xx is the site number. There are multiple computers on the network with names similar to "TERMINALxx_yy", where xx is the site number and yy is the number of the TERMINALS. What I would like to do is find the lowest numbered of the "TERMINALS" (as 1 may not always be the lowest). There is an environment variable on the SERVER named TERMSTR that is equal to "TERMINALxx_", as well as an environment variable named NUMTERMS that is the number of TERMINALS at the site.
The most I've been able to figure out is:
net view | Select-string $termstr
But that just gives the table output.
I'm figuring I need to first have NET VIEW give just the computer names, then sort in descending order and select the first one.
Thanks
if you absolutely MUST use net view, then this will give you the highest lowest system number with the header, footer, line padding, and non-digits removed. it does not add any leading zeros to the resulting number, tho. [grin]
net view |
# skip the 3 header lines
Select-Object -Skip 3 |
# skip the footer lines
Select-Object -SkipLast 2 |
# trim away the "net view" line padding
# remove the non-digits
ForEach-Object {
[int]($_.Trim() -replace '[^0-9]')
} |
Sort-Object |
Select-Object -First 1
Here is a starting point of code you can use.
$Servers = Get-ADComputer -Filter 'Name -like "SERVER*"'
foreach($Server in $Servers | Sort-Object){
$N = $Server.name.substring(($Server.name.length)-2)
$Terminals = Get-ADComputer -Filter 'Name -like "TERMINAL$($N)*"'
$count = $Terminals.Count
$Terminal = $Terminals | Sort-Object
$TerminalZero = $Terminal[0].Name
Write-Host "Terminal Name: $TerminalZero"
$COMMAND = {
Write-Host [System.Environment]::GetEnvironmentVariable("termstr","Machine")
[System.Environment]::SetEnvironmentVariable("TERMSTR", $TerminalZero, "Machine")
[System.Environment]::SetEnvironmentVariable("NUMTERMS", $Count, "Machine")
}
Invoke-Command -ComputerName $Server -ScriptBlock { $COMMAND }
}
}

powershell match multiple variables

So I read stackoverflow a lot and it always helps me with my understanding, at the moment, I'm a bit stumped on the matching any of multiple variables and as such, thought I'd ask the exports.
A bit of background on my task.
I am a systems admin for an AD forest with multiple child domains (+4), we are going through a process to consolidate these domains into one - however, unfortunately, as each was managed independently we have comes across the age old 'duplicate SAM' issue (and will for sure come across dupe UPNs too once we start digging).
As such - I am trying to build a script to find these duplicates so I can address them with the teams who support the users in these domains.
I've got a fair way through this, I've exported a list of the SAMs for one migrating domain, and I can parse this into PS and have it look up each SAM against all of my domains and either give me a variable with the username OR $null.
So, I now just want to understand what the 'IF' command is for matching more than one variable and showing which ones match.
So, for example, from these variables...
$one="MSmith01"
$oneNT="DOMAIN1\MSmith01"
$two="MSmith01"
$twoNT="DOMAIN2\MSmith01"
$three=$null
$threeNT=$null
$four=$null
$fourNT=$null
$five="MSmith01"
$fiveNT="DOMAIN5\MSmith01"
$six=$null
$sixNT=$null
How do I write the IF command to show me if more than one variable matches??
If I write this:
if ($one -match $two -match $three -match $four -match $five -match $six) {write-host "Duplicate Found - $oneNT, $twoNT, $threeNT, $fourNT, $fiveNT, $sixNT." -foregroundcolor "yellow"}
It will show me :
Duplicate Found - DOMAIN1\MSmith01, DOMAIN2\MSmith01, , , DOMAIN5\MSmith01,.
But, if the variables are:
$one=$null
$oneNT=$null
$two="MSmith01"
$twoNT="DOMAIN2\MSmith01"
$three="MSmith01"
$threeNT="DOMAIN3\MSmith01"
$four=$null
$fourNT=$null
$five=$null
$fiveNT=$null
$six=$null
$sixNT=$null
Then the result seems to ignore the fact that DOMAIN 2 and DOMAIN3 have matching SAMs and it appears that "x -match y -match z" is really actually only comparing x and y, so I get nothing.
Which is obviously incorrect.
Can anyone explain how I compare and look for ANY duplicate amongst several variables?
Thanks
To further elaborate:
To get my variables I'm doing this:
$SAMAccountList=gc "C:\My Documents\Domain2SAMList.txt"
$SamAccountList | % {
$one=Get-QADUser -SamAccountName $_ -IncludedProperties SamAccountName -Server dc.domain1.local | foreach { $_.SAMAccountName }
$oneNT=Get-QADUser -SamAccountName $_ -IncludedProperties NTAccountName -Service dc.domain1.local | foreach { $_.NTAccountName }
$twoSAM=Get-QADUser -SamAccountName $_ -IncludedProperties SamAccountName -Service dc.domain2.local | foreach { $_.SAMAccountName }
$twoNT=Get-QADUser -SamAccountName $_ -IncludedProperties NTAccountName -Service dc.domain2.local | foreach { $_.NTAccountName }
#...
#...
#...etc for each domain
}
This then gives me a set of variables as I mentioned above
I would put them in a customobject and search with the unique names.
$NAMES = #()
$NAMES += [pscustomobject] #{name=$one;nameNT=$oneNT}
$NAMES += [pscustomobject] #{name=$two;nameNT=$twoNT}
$NAMES += [pscustomobject] #{name=$three;nameNT=$threeNT}
$NAMES += [pscustomobject] #{name=$four;nameNT=$fourNT}
$NAMES += [pscustomobject] #{name=$five;nameNT=$fiveNT}
$NAMES += [pscustomobject] #{name=$six;nameNT=$sixNT}
$UniqueNames=($NAMES | Sort-Object -Property name -Unique).Name
ForEach($Uniquename in $UniqueNames){
if($Uniquename -ne $NULL){
$SEARCH_RESULT=$NAMES|?{$_.Name -eq $Uniquename}
if ( $SEARCH_RESULT.Count -gt 1)
{
$SEARCH_RESULT
}
}
}
I get the following results
First data set
name nameNT
---- ------
MSmith01 DOMAIN1\MSmith01
MSmith01 DOMAIN2\MSmith01
MSmith01 DOMAIN5\MSmith01
Second data set
name nameNT
---- ------
MSmith01 DOMAIN2\MSmith01
MSmith01 DOMAIN3\MSmith01

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.

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