I can thoroughly recommend Debuggex for visualizing and testing regular expressions if you aren't fluent in them! There are also cheat sheets available and don't worry that there isn't a drop down for powershell the default is good enough to get you where you need to be.
So now we are automating the build it made sense to automate the naming of servers, and given the vast size of our estate and the number of non joined up systems, then checking of names is a bit error prone and time consuming. Given we can do it manually, how hard could it be to automate it?
I did a bit of googling and found this post on stackoverflow which was a nice starting point. It didn't fully work and you had to read between the lines.
After a bit of tweaking I got it working so we could fill in the gaps in sequences, IE 1,2,3,5,6,7 would produce the next server as 4. However 3,4,5,6,7 would produce the next server number as 8. A quick fix to the code to make it think a server 0 already exists fixes that issue. (NB: our server numbers start at 001, not 000 - if your's don't then you may need to add -1 instead - Untested!)
The next issue is that, not every server is domain joined so you need to query more than just AD.. For us I wanted to query a couple of SCCM environments (2007 and 2012 r2) SCOM, Multiple VMware environments, and also DNS.
So the code I have produced (which can be re-factored and probably will be at some point) queries each environment getting just the digits of the server name and add's them to the array.
At the end of the script we combine all of the array's and add the 0 previously mentioned and then strip duplicates and sort into numerical order.. this gives us our definitive list of numbers in use. (we work on worse case, if we see it anywhere then assume its in use)
Once we find the name we create an entry in DNS so that subsequent script use wont issue the same name - we deal with concurrency issues by running this in System Center Orchestrator and setting job concurrency on the runbook to be 1.
Script below, change values to suit your environment.
# Add this script can support -verbose (Convert to function later) [CmdletBinding()] param() # ******************************************************** $startOfName = "xxxYYYZZWEB" # ******************************************************** # VMWare Details $ADVIServers = @("vsphere1.blah.local","vsphere2.blah.local","vsphere3.blah.local","vsphere4.blah.local") $StandAloneHosts = @() # DNS Details $DNSServer = "xxxxxx.blah.local" # SCCM 2012 Details $SCCM2012SiteServer = "sccm2012.blah.local" $SCCM2012SiteCode = 'SiteCode' # SCCM 2007 Details $SCCM2007SiteServer = "sccm2007.blah.local" $SCCM2007SiteCode = 'SiteCode2' # SCOM 2007 Details $SCOMServer = "scom.blah.local" # Create Empty Arrays $VMNumbers = @() $ADnumbers = @() $DNSNumbers = @() $SCCM2012Numbers = @() $SCCM2007Numbers = @() $SCOM2007Numbers = @() # VMWare Write-Verbose "Processing VMware" Add-PSSnapin vmware.vimautomation.core -ErrorAction SilentlyContinue # Set options for certificates and connecting to multiple enviroments $null = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$False $null = Set-PowerCLIConfiguration -DefaultVIServerMode Multiple -Scope User -Confirm:$False # Connect to each AD Authenticated viServer foreach ($VIServer in $ADVIServers){$null = Connect-VIServer $VIServer -verbose:$false} # Connect to standalone host foreach ($Host in $StandAloneHosts){$null = Connect-VIServer $Host -User 'usernamehere' -Password 'passwordhere' -verbose:$false} # get next available number in a range of numbers. $VMNames = Get-VM -Name "$($startOfName)*" -verbose:$false |select Name $VMNames |select Name | Foreach-Object {Write-Verbose $_.Name} | Sort-Object $VMNumbers = $VMNames |select Name | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object Write-Verbose "$($VMNumbers.Count) Matching entries found" # Active Directory Write-Verbose "Processing Active Directory" # Issue Query $searcher = [ADSISearcher]"(&(objectCategory=computer)(name=$($StartOfName)*))" $searcher.PageSize = 1000 # get next available number in a range of numbers. returns 5 for 1,2,3,4,6,7,9 From AD $ADNames = $searcher.FindAll() | Foreach-Object {[string]$_.Properties.name} | Sort-Object $ADNames | Foreach-Object {Write-Verbose $_} | Sort-Object $ADnumbers = $ADNames | Foreach-Object {[int]($_ -replace '\D').Trim() } | Sort-Object Write-Verbose "$($ADnumbers.Count) Matching entries found" # Search DNS Write-Verbose "Processing DNS" # Import DNS module Import-Module dnsShell -Verbose:$false $DNSNames = get-dnsRecord -server $DNSServer -RecordType A -Zone blah.local | select Name |where {$_.Name -like "$($startOfName)*"} $DNSNames | Foreach-Object {Write-Verbose $_.Name} | Sort-Object -Unique $DNSNumbers = $DNSNames | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object -Unique Write-Verbose "$($DNSNumbers.Count) Matching entries found" # Search SCCM Write-Verbose "Processing SCCM 2012" # Query SCCM2012 Env $SCCM2012Members = Get-WmiObject -ComputerName $SCCM2012SiteServer -Namespace "ROOT\SMS\site_$SCCM2012SiteCode" -Query "SELECT * FROM SMS_FullCollectionMembership WHERE CollectionID='SMS00001' AND Name LIKE '$($startOfName)%' order by name" | select Name -Unique $SCCM2012Members |select Name | Foreach-Object {Write-Verbose $_.Name} | Sort-Object $SCCM2012Numbers = $SCCM2012Members |select Name | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object Write-Verbose "$($SCCM2012Numbers.Count) Matching entries found" Write-Verbose "Processing SCCM 2007" # Query SCCM2007 Env $SCCM2007Names = Get-WMIObject -ComputerName $SCCM2007SiteServer -Namespace "root\sms\site_$SCCM2007SiteCode" -class "SMS_R_System" -filter "Name LIKE `"$startOfName%`"" |select Name | Sort-Object -Property Name -Unique $SCCM2007Names |select Name | Foreach-Object {Write-Verbose $_.Name} | Sort-Object $SCCM2007Numbers = $SCCM2007Names |select Name | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object Write-Verbose "$($SCCM2007Numbers.Count) Matching entries found" # Search Production SCOM 2007 Write-Verbose "Processing SCOM 2007" #Initialize SCOM SnapIn Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client -ErrorAction SilentlyContinue -verbose:$false #Connect to Production SCOM 2007 Env. $null = New-ManagementGroupConnection -ConnectionString $SCOMServer #Connect to SCOM Provider Push-Location 'OperationsManagerMonitoring::' # Get Agents Matching Name $SCOM2007Names = Get-ManagementServer |Get-Agent |Where {$_.Name -like "$($startOfName)*"} $SCOM2007Names | Foreach-Object {Write-Verbose $_.Name} | Sort-Object $SCOM2007Numbers = $SCOM2007Names | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object Write-Verbose "$($SCOM2007Numbers.Count) Matching entries found" # Return to previous location Pop-Location # Merge arrays adding a zero so we allways start issuing numbers from the beginning (ie 001) $list = @(0) + $VMNumbers + $ADnumbers + $DNSNumbers + $SCCM2012Numbers + $SCCM2007Numbers + $SCOM2007Numbers # Remove Duplicates numbers from the array and sort into numerical order $list = $list | Sort-Object -Unique Write-Verbose "Used numbers after sorting: $($list)" # Determine if next server name is a gap in the sequence in the array for($i=0; $i -lt $list.length; $i++) { if( $list[$i+1]-$list[$i] -gt 1) { # The gap between the current server number and the next element in the array is greater than 1 # So we have an available number we can use. # TODO: - Add support for consecutive numbers IE build 6 servers with consecutive numbers. $num = "{0:000}" -f ($list[$i]+1) break } } # If no gap found in the sequence then use the next number from the sequence in the array if ($num -eq $null) { $num = "{0:000}" -f (($list[-1]+1)) } # Construct new name $NewComputerName = "{0}{1}" -f $startOfName,$num # Create DNS Record to 'reserve / mark the name as in use' Write-Verbose "Creating DNS Reservation" New-DnsRecord -Name $NewComputerName -IPAddress "127.0.0.1" -Zone blah.local -Type A -Server $DNSServer write-output $NewComputerName