Join arrays in PowerShell similar to concatenating DataFrame columns - powershell

Is there a way to join arrays in PowerShell similar to concatenating DataFrame columns in Python Pandas with concat or in R with cbind rather than iterating through every item?
Below is a reproducible example that binds four arrays together as four columns in a PowerShell object. How would I get rid of the for loop and get the same results?
$LogicalProcessors = (Get-WmiObject –class Win32_processor
-Property NumberOfLogicalProcessors).NumberOfLogicalProcessors;
function myTop([String]$SortCol='CPU', [Int32]$top=30) {
$NameArray = get-counter '\Process(*)\ID Process' -EA SilentlyContinue |
Select -Expand CounterSamples | Select InstanceName, CookedValue
$CpuArray = get-counter '\Process(*)\% Processor Time' -EA SilentlyContinue |
Select -Expand CounterSamples | Select CookedValue
$MemArray = get-counter '\Process(*)\Working Set - Private' -EA SilentlyContinue |
Select -Expand CounterSamples | Select CookedValue
$TopTable = For ($i=0; $i -lt $NameArray.Length; $i++) {
if ($NameArray[$i].InstanceName -eq '_total') {continue}
if ($NameArray[$i].InstanceName -eq 'memory compression') {continue}
if ($NameArray[$i].InstanceName -eq 'idle') {
$CPU = ($CpuArray[$i].CookedValue) / $LogicalProcessors;
} else {
$CPU = $CpuArray[$i].CookedValue;
}
[PSCustomObject]#{
Name = $NameArray[$i].InstanceName;
ID = $NameArray[$i].CookedValue;
CPU = $CPU;
Memory = $MemArray[$i].CookedValue;
}
}
$TopTable | sort -des $SortCol | select -f $top |`
select Name, ID,`
#{Name='CPU'; Expression = {("{0:N1}%" -f $_.CPU) } },`
#{Name='Memory'; Expression = {("{0:N0} K" -f ($_.Memory /1kb) )} }
}
myTop -SortCol Memory -top 30 | ft -a

I think PowerShell doesn't provide a way to combine columns. In this case, it can correspond by Group-Object.
function myTop([string]$SortCol = "CPU", [int]$Top = 30)
{
$LogicalProcessors = (Get-WmiObject Win32_processor NumberOfLogicalProcessors).NumberOfLogicalProcessors
Get-Counter '\Process(*)\ID Process','\Process(*)\% Processor Time','\Process(*)\Working Set - Private' -ea SilentlyContinue |
foreach CounterSamples |
where InstanceName -notin "_total","memory compression" |
group { $_.Path.Split("\\")[3] } |
foreach {
[pscustomobject]#{
Name = $_.Group[0].InstanceName
ID = $_.Group[0].CookedValue
CPU = if($_.Name -eq "idle") { $_.Group[1].CookedValue / $LogicalProcessors } else { $_.Group[1].CookedValue }
Memory = $_.Group[2].CookedValue / 1KB
}
} |
sort -des $SortCol |
select -f $Top #(
"Name","ID"
#{ n = "CPU"; e = { ("{0:N1}%" -f $_.CPU) } }
#{ n = "Memory"; e = { ("{0:N0} K" -f $_.Memory) } }
)
}
myTop -SortCol Memory -top 10 | ft -a

Related

Speed up PowerShell Get-Counter and Get-Process

The following PowerShell code works, generates the output shown below, and is reproducible.
The general question is: how can I speed it up?
The biggest bottlenecks are the Get-Process and Get-Counter cmdlets.
EDITED based on #LotPings comments.
$LogicalProcessors = (Get-WmiObject –class Win32_processor
-Property NumberOfLogicalProcessors).NumberOfLogicalProcessors;
function myTop([String]$SortCol='CPU', [Int32]$top=30) {
$NameArray = get-counter '\Process(*)\ID Process' -EA SilentlyContinue |
Select -Expand CounterSamples | Select InstanceName
$IdArray = get-counter '\Process(*)\ID Process' -EA SilentlyContinue |
Select -Expand CounterSamples | Select CookedValue
$CpuArray = get-counter '\Process(*)\% Processor Time' -EA SilentlyContinue |
Select -Expand CounterSamples | Select CookedValue
$MemArray = get-counter '\Process(*)\Working Set - Private' -EA SilentlyContinue |
Select -Expand CounterSamples | Select CookedValue
$procTbl = get-process | select ID, Name, Description, MainWindowTitle
$TopTable = #()
For ($i=0; $i -lt $NameArray.Length; $i++) {
$procIndex = [array]::indexof($procTbl.ID, [Int32]$IdArray[$i].CookedValue)
if ($NameArray[$i].InstanceName -eq '_total') {continue}
if ($NameArray[$i].InstanceName -eq 'memory compression') {continue}
if ($NameArray[$i].InstanceName -eq 'idle') {
$NewRow = [PSCustomObject]#{
Name = $NameArray[$i].InstanceName;
ID = $IdArray[$i].CookedValue;
CPU = ($CpuArray[$i].CookedValue) / $LogicalProcessors;
Mem = $MemArray[$i].CookedValue;
procID = $procTbl.ID[$procIndex];
Description = $procTbl.Description[$procIndex];
Title = $procTbl.MainWindowTitle[$procIndex];
}
$TopTable += $NewRow
} else {
$NewRow = [PSCustomObject]#{
Name = $NameArray[$i].InstanceName;
ID = $IdArray[$i].CookedValue;
CPU = $CpuArray[$i].CookedValue;
Mem = $MemArray[$i].CookedValue;
procID = $procTbl.ID[$procIndex];
Description = $procTbl.Description[$procIndex];
Title = $procTbl.MainWindowTitle[$procIndex];
}
$TopTable += $NewRow
}
}
$TopTable | sort -des $SortCol | select -f $top |`
select Name, ID,`
#{Name='CPU'; Expression = {("{0:N1}%" -f $_.CPU) } },`
#{Name='Mem'; Expression = {("{0:N0} K" -f ($_.Mem /1kb) )} },
Description, Title
}
While(1) {$p = myTop -SortCol CPU -top 12 | ft -a ; sleep 0; cls; $p}
Output (refresh rate approximately 2 seconds):
Name ID CPU Mem Description Title
---- -- --- --- ----------- -----
memory compression 2768 0.0% 996,516 kb
code 8828 0.0% 232,084 kb Visual Studio Code
chrome 28620 0.0% 217,088 kb Google Chrome
code 41596 0.0% 180,076 kb Visual Studio Code
chrome 33772 0.0% 140,976 kb Google Chrome Speed up PowerShell Get-Counter and Get-Pro...
code 22600 0.0% 127,952 kb Visual Studio Code
teams 27412 0.0% 105,656 kb Microsoft Teams
powershell 55692 1.5% 95,596 kb Windows PowerShell
mcshield 8596 0.0% 93,784 kb
onedrive 59644 0.0% 80,796 kb Microsoft OneDrive
code 21708 0.0% 79,688 kb Visual Studio Code
powershell 64228 0.0% 74,508 kb Administrator: Windows PowerShell
Due to my locale with different counter names, I can't test myself:
## Q:\Test\2019\04\14\SO_55678790.ps1
$LogicalProcessors = (Get-WmiObject –class Win32_processor).NumberOfLogicalProcessors;
function myTop([String]$SortCol='CPU', [Int32]$top=30) {
$NameArray = get-counter '\Process(*)\ID Process' -EA SilentlyContinue |
Select -Expand CounterSamples | Select-Object InstanceName,CookedValue
$CpuArray = get-counter '\Process(*)\% Processor Time' -EA SilentlyContinue |
Select -Expand CounterSamples | Select-Object CookedValue
$MemArray = get-counter '\Process(*)\Working Set - Private' -EA SilentlyContinue |
Select -Expand CounterSamples | Select-Object CookedValue
$proc = Get-Process
$TopTable = For ($i=0; $i -lt $NameArray.Length; $i++) {
$procIndex = [array]::indexof($proc.Id, [Int32]$NameArray[$i].CookedValue)
if ($NameArray[$i].InstanceName -eq '_total') {continue}
if ($NameArray[$i].InstanceName -eq 'idle') {
$CPU = ($CpuArray[$i].CookedValue) / $LogicalProcessors
} else {
$CPU = $CpuArray[$i].CookedValue;
}
[PSCustomObject]#{
Name = $NameArray[$i].InstanceName
ID = $NameArray[$i].CookedValue
CPU = $CPU
Mem = $MemArray[$i].CookedValue
Description = $proc[$procIndex].Description
Title = $proc[$procIndex].MainWindowTitle
}
}
$TopTable | Sort-Object -Descending $SortCol | Select-Object -First $top Name,ID,
#{Name='CPU'; Expression = {("{0:N1}%" -f $_.CPU) } },
#{Name='Mem'; Expression = {("{0:N0} kb" -f ($_.Mem /1kb) )} },
Description, Title
}
While(1) {
myTop -SortCol CPU -top 12 | ft -a
sleep 0
cls
# pause
}

Array to table in powershell

Help me with some problem. I am new in PS and
i need output data to table view.
Looks like this.
name1 name2
----- -----
value1 value2
But i have:
$a=(get-service).name
$b=(get-service).Status
foreach ($name in $a)
{
$data = [pscustomobject]#{name1 = $name}
}
$data
RESULT
name1
-----
XboxNetApiSvc
WITHOUT FOREATCH
$a=(get-service).name
$b=(get-service).Status
$data = [pscustomobject]#{name1 = $a ; name2 = $b }
$data
Result
name1 name2
----- -----
{2GISUpdateService, AcronisActiveProtectionService, AcrSch2Svc, AdobeARMservice...} {Stopped, Running, Running, Running...}
All of that i need for this script
$list = ((Get-ADComputer -SearchBase "OU=PC,DC=cbu,DC=lan" -Filter {Enabled -eq "True" } -Properties DNSHostName).DNSHostName)
$encoding = [System.Text.Encoding]::UTF8
$up = #()
$down = #()
$table= #()
foreach ($pc in $list)
{
if ((Test-Connection -Count 1 -computer $pc -quiet) -eq $True)
{
$up += $pc
#write-host $pc "is up"
}
else
{
$down += $pc
#Write-Host $pc "down"
}
}
After all i need output values of $up and $down in 2 columns
You probably have a custom commandlet but you can run something similar to:
(Get-Service) | select Name,Status | Format-Table
UPDATE
After reading your update. At the end of your script you have two arrays $up and $down. I will declare it the static way to make an example easier
$up = #('pc1', 'pc2')
$down = #('pc3','pc4', 'pc5')
Because arrays can be diffrent length you need to calculate maximum length with:
$max = ($up, $down | Measure-Object -Maximum -Property Count).Maximum
And than create an object which "merges" above arrays with:
0..$max | Select-Object #{n="Up";e={$up[$_]}}, #{n="Down";e={$down[$_]}}
The output is:
Up Down
-- ----
pc1 pc3
pc2 pc4
pc5
If you are using Get-Service as example only then, you can just use Select :
Get-Service | Select Name, Status
Else
$MyList | Select Name1, Name2
Moreover, if you have a complex command and you want to extract a table of PSObject :
Get-Process | Select-Object -Property ProcessName, Id, WS
Read more about Select-Object : https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/select-object?view=powershell-4.0
Yes Piotr Stapp is rights. You can use Format-Table
Example 1:
Get-Process | Sort-Object starttime | Format-Table -View starttime
Example 2:
(Get-Service) | select Name,Status | Format-Table
Example 3:
Get-Process | FT

Powershell: Out-File with Headers/Columns

I am very close to what I am looking for, I have all of the correct data and it is just not formatting correctly. Currently it is all in one column, and what I would like is for the headers to be in a row, and then each entry to be in its own row as well. Here is my current code:
$servers = Get-ADComputer -Filter "Name -like 'f****p*'" | Sort-Object | Select -ExpandProperty Name
$servers | foreach {
$server = $_
$serverIP = Test-Connection -ComputerName $server -Count 1 | Select -ExpandProperty IPV4Address
$serverIPString = $serverIP.IPAddresstoString
$scope = Get-DhcpServerv4Scope -ComputerName $server
$options = Get-DhcpServerv4OptionValue -ComputerName $server
New-Object -TypeName psobject -Property #{
ServerName = $server
ServerIP = $serverIPString
ScopeName = $scope.Name | Out-String
StartIP = $scope.StartRange.IPAddressToString | Out-String
EndIP = $scope.EndRange.IPAddressToString | Out-String
SubnetMask = $scope.SubnetMask.IPAddressToString | Out-String
Duration = $scope.LeaseDuration.Days | Out-String
OptionName = $options.Name | Out-String
OptionID = $options.OptionID | Out-String
OptionValue = $options.Value | Out-String
}
} | Select-Object ServerName, ServerIP, ScopeName, StartIP, EndIP, SubnetMask, Duration, OptionName, OptionID, OptionValue | Out-File C:\temp\FPScopes.csv
Currently the output looks like this example (using Duration and Options as an example, as it has the most generic data to share):
What I am looking for would be to have each object type as a column header, and have the entries in a separate row underneath the header:
Etc....
Any assistance would be greatly appreciated.
Thank you! :)
Ry
My final code, after help from this forum:
Add-Content -Value "ServerName,ServerIP,ScopeName,StartRange,EndRange,SubnetMask,Duration,OptionName,OptionID,Value" -Path c:\temp\POSScopes.csv
Get-ADComputer -Filter {Name -like 'p****p01' -or Name -like 'p****p02' -or Name -like 'q****001' -or Name -like 'q****002'} | Sort-Object | Select -ExpandProperty Name | Out-File C:\Temp\POSServers.txt
$servers = Get-Content C:\Temp\POSServers.txt
$servers | foreach {
$server = $_
$serverIP = Test-Connection -ComputerName $server -Count 1 | Select -ExpandProperty IPV4Address
$serverIPString = $serverIP.IPAddresstoString
$scope = Get-DhcpServerv4Scope -ComputerName $server
$options = Get-DhcpServerv4OptionValue -ComputerName $server
New-Object -TypeName psobject -Property #{
ServerName = $server
ServerIP = $serverIPString
ScopeName = $scope.Name | Out-String
StartIP = $scope.StartRange.IPAddressToString | Out-String
EndIP = $scope.EndRange.IPAddressToString | Out-String
SubnetMask = $scope.SubnetMask.IPAddressToString | Out-String
Duration = $scope.LeaseDuration.Days | Out-String
OptionName = $options.Name | Out-String
OptionID = $options.OptionID | Out-String
OptionValue = $options.Value | Out-String
}
for ($i = ($Options.Count -1); $i -gt -1; $i--) {
Add-Content -Value "$($server),$($serverIPString),$($scope.Name),$($scope.StartRange.IPAddressToString),$($scope.EndRange.IPAddressToString),$($scope.SubnetMask.IPAddressToString),$($scope.LeaseDuration.Days),$($options[$i].Name),$($options[$i].OptionID),$($Options[$i].Value)" -Path C:\temp\POSScopes.csv
}
}
Add-Content -Value "ServerName,ServerIP,ScopeName,StartRange,EndRange,SubnetMask,Duration,OptionName,OptionID,Value" -Path c:\temp\POSScopes.csv
Get-ADComputer -Filter {Name -like 'p****p01' -or Name -like 'p****p02' -or Name -like 'q****001' -or Name -like 'q****002'} | Sort-Object | Select -ExpandProperty Name | Out-File C:\Temp\POSServers.txt
$servers = Get-Content C:\Temp\POSServers.txt
$servers | foreach {
$server = $_
$serverIP = Test-Connection -ComputerName $server -Count 1 | Select -ExpandProperty IPV4Address
$serverIPString = $serverIP.IPAddresstoString
$scope = Get-DhcpServerv4Scope -ComputerName $server
$options = Get-DhcpServerv4OptionValue -ComputerName $server
New-Object -TypeName psobject -Property #{
ServerName = $server
ServerIP = $serverIPString
ScopeName = $scope.Name | Out-String
StartIP = $scope.StartRange.IPAddressToString | Out-String
EndIP = $scope.EndRange.IPAddressToString | Out-String
SubnetMask = $scope.SubnetMask.IPAddressToString | Out-String
Duration = $scope.LeaseDuration.Days | Out-String
OptionName = $options.Name | Out-String
OptionID = $options.OptionID | Out-String
OptionValue = $options.Value | Out-String
}
for ($i = ($Options.Count -1); $i -gt -1; $i--) {
Add-Content -Value "$($server),$($serverIPString),$($scope.Name),$($scope.StartRange.IPAddressToString),$($scope.EndRange.IPAddressToString),$($scope.SubnetMask.IPAddressToString),$($scope.LeaseDuration.Days),$($options[$i].Name),$($options[$i].OptionID),$($Options[$i].Value)" -Path C:\temp\POSScopes.csv
}
}

Output from WMI queries not coming out correctly

I am querying the WMI class Win32_ShadowCopy for disk information to check for multiple things. Status code, Last Snapshot, etc. I can run this script on the local machine and it returns the correct information. Once I use Import-Csv that contains the servers and disk to check shadowcopy health it returns the wrong output.
Example Input 1:
Server Drive
inms145 C
inms145 D
inms145 E
mission-1 C
mission-1 Q
mission-1 V
mission-1 W
mission-1 T
mission-1 X
mission-1 U
mission-1 Z
Example output (wrong):
ServerName Last_Snapshot Volume Condition SCDrive LsDrives Status
INMS145 20160621135802.865078-240 \\?\Volume{022366e6-d8a5-49fc-8c82-263032d6c6a3}\ OK - Status Code: 12 E C D E C Q V W T X U Z Enabled Correctly on E
INMS145 20160621135802.865078-240 \\?\Volume{022366e6-d8a5-49fc-8c82-263032d6c6a3}\ OK - Status Code: 12 E C D E C Q V W T X U Z Enabled Correctly on E
Example Input 2:
Server Drive
inms145 C
inms145 D
inms145 E
Example output (correct):
ServerName Last_Snapshot Volume Condition SCDrive LsDrives Status
INMS145 20160621135802.865078-240 \\?\Volume{022366e6-d8a5-49fc-8c82-263032d6c6a3}\ OK - Status Code: 12 E C D E Enabled Correctly on E
INMS145 N/A N/A NOT ENABLED C D E C D E Not Enabled on C D E
Script:
$CSVPath = "\\SERVER\E$\Utilities\Scripts\Set_DashboardData\ShadowCopy\Servers_Drives.csv"
$rPath = "\\SERVER\E$\Utilities\Scripts\Set_DashboardData\ShadowCopy\Results.csv"
$Pshell2F = "\\SERVER\E$\Utilities\Scripts\Set_DashboardData\ShadowCopy\export-csv-pshell.2.ps1"
$SCHash = #()
$SCArray = #()
if ((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName).ProductName -like "*Windows Server 2008*") {
$Pshell2F | Out-Null
$SC_Get = Import-Csv $CSVPath | ForEach-Object {
Get-WmiObject -ComputerName $_.server -Class Win32_ShadowCopy | foreach {
$_ | Select-Object Volumename,#{n="SCDrive";e={$vol08=$_.volumename;(gwmi -Class Win32_Volume | select deviceid | ? {$_.deviceid -eq $vol08}).DeviceID}}
}
}
} elseif ((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName).ProductName -like "*Windows Server 2012*") {
$SC_Get = Import-Csv $CSVPath | ForEach-Object {
Get-WmiObject -ComputerName $_.server -Class Win32_ShadowCopy | foreach {
$_ | Select-Object PSComputerName,InstallDate,Volumename,State,#{n="SCDrive";e={$vol12=$_.volumename;(Get-Volume | ? {$_.path -eq $vol12}).DriveLetter}},#{n="LsDrives";e={Import-Csv $CSVPath | select -ExpandProperty drive}}
}
}
}
$SC_Get | foreach {
$props = #{
ServerName = $_.PSComputerName
Last_Snapshot = $_.InstallDate
Volume = $_.Volumename
Condition = $_.State
SCDrive = $_.SCDrive
LsDrives = $_.LsDrives
Status = $_.State
}
New-Object PsObject -Property $props
} | ForEach-Object {
$SCHash = New-Object -TypeName PSObject -Property #{
'ServerName' = $_.ServerName
'Last_Snapshot' = $_.Last_Snapshot
'Volume' = $_.Volume
'Condition' = $_.Condition
'SCDrive' = $_.SCDrive
'LsDrives' = $_.LsDrives
'Status' = $_.Status
}
#The altered data is put into the empty array created earlier and the select statement specifies the order to put it in.
$SCArray += $SCHash | Select-Object ServerName,Last_Snapshot,Volume,Condition,SCDrive,LsDrives,Status
}
$SC = $SCArray
$SCFilt = $SCArray | Select-Object -ExpandProperty LsDrives | Where-Object { $_ -notmatch $($SC | Select-Object -ExpandProperty SCDrive)} | select -Unique
$SCArray | ForEach-Object {
if ($_.LsDrives -match $_.SCDrive -and $_.Last_Snapshot -like "") {
$_.Status = "Enabled - No Snaps"
$_.Condition = "NO SNAPSHOTS - Status Code: " + ($($_.Condition))
}
if ($_.LsDrives -match $_.SCDrive -and $_.Last_Snapshot -like "**" -and $_.Status -ne "12") {
$_.Status = "Enabled-Snaps-State doesnt equal 12"
$_.Condition = "NOT OK - Status Code: " + ($($_.Condition))
}
if ($_.LsDrives -match $_.SCDrive -and $_.Last_Snapshot -like "**" -and $_.Status -eq "12") {
$_.Status = "Enabled Correctly on " + ($($_.SCDrive))
$_.Condition = "OK - Status Code: " + ($($_.Condition))
}
}
$SCArray | select -Unique | Export-Csv $rPath -NoTypeInformation -Force | Out-Null
$SCArray | ForEach-Object {
if ($SCFilt) {
$_.Status = "Not Enabled on " + ($($SCfilt))
$_.Condition = "NOT ENABLED"
$_.SCDrive = "$($SCFilt)"
$_.Last_Snapshot = 'N/A'
$_.Volume = 'N/A'
}
}
$SCArray | select -Unique | Export-Csv $rPath -NoTypeInformation -Force -Append | Out-Null
How do I get the output to correlate with the next server in the list? It is also combining the column of drives and not running the information from the other server. If I run $SCArray | select -Unique, it will only return the inms145 even though mission-01 is in that list.

Find next available computer name

I'm trying to find the next available computer name in out domain. Our computers use a naming format
departmentName001
departmentName003
departmentName004
...
departmentName999
I can find the existing computer accounts and add 1 but I can't work out for to get it to start looking at 001, I'm aware of the use of "{0:d3}" -f but I'm not using it correctly. Can anyone help?
function GetComputerList($ComputerName)
{
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = “LDAP://dc=domain,dc=local”
$objSearcher.Filter = ("(&(objectCategory=computer)(name=$ComputerName))")
$colProplist = "name"
$objSearcher.PageSize = 1000
foreach ($i in $colPropList){[void]$objSearcher.PropertiesToLoad.Add($i)}
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults)
{$objComputer = $objResult.Properties; $objComputer.name}
}
$HostName = Finance
$unit="{0:d3}" -f $_
$num = GetComputerList("$HostName*") | Foreach {[int]($_.Name)} | Sort-Object | Select-Object -Last 1
$name = $HostName+($unit+($num+1))
Try this, it gets all computer with name starting with 'departmentName', strips all a-z characters, leaving just the numbers, converts the numbers to integers and sorting them to find the largest one:
$searcher = [ADSISearcher]'(&(objectCategory=computer)(name=departmentName*))'
$searcher.PageSize = 1000
$last = $searcher.FindAll() | Foreach-Object { [int]($_.Properties.name -replace '\D').Trim() } | Sort-Object | Select-Object -Last 1
$digitLength = "$last".Length
$NewComputerName = "{0}{1:D$digitLength}" -f 'departmentName',($last+1)
$NewComputerName
EDIT:
# get next available number in a range of numbers. returns 5 for 1,2,3,4,6,7,9
$number = $searcher.FindAll() | Foreach-Object { [int]($_.Properties.name -replace '\D').Trim() } | Sort-Object
for($i=0; $i -lt $number.length; $i++) {if( $number[$i+1]-$number[$i] -gt 1) {$number[$i]+1; break} }
try this:
$searcher = [ADSISearcher]'(&(objectCategory=computer)(name=Finance*))'
$searcher.PageSize = 1000
$last = $searcher.FindAll() | Foreach-Object {
[string]($_.Properties.name -replace '\D') } | Sort-Object
$i = 0
$last | % { if ($i -ne [int]$_ ) { $new = $i.tostring().padleft(3,'0'); break }
else
{ $i++ }}
$newComputerName = "finance" + $new
based on the information in this post I have made a few changes and tweaks to the code for my environment to check more than just AD.. and also fixed it not filling in blanks at the start of a range.. I have blogged it here: AutoGeneratingServer Names
copy of the code here too, and I know it can be refactored lots!
[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