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 }
}
}
Related
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
I have a script for retrieving AD site and subnet info from the forest. Need to add the location details also to the script
The script is tested and working fine where it gives site and subnet details.
$configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
$siteContainerDN = ("CN=Sites," + $configNCDN)
$siteObjs = Get-ADObject -SearchBase $siteContainerDN -filter { objectClass -eq "site" } -properties "siteObjectBL", name
foreach ($siteObj in $siteObjs) {
$subnetArray = New-Object -Type string[] -ArgumentList $siteObj.siteObjectBL.Count
$i = 0
foreach ($subnetDN in $siteObj.siteObjectBL) {
$subnetName = $subnetDN.SubString(3, $subnetDN.IndexOf(",CN=Subnets,CN=Sites,") - 3)
$subnetArray[$i] = $subnetName
$i++
}
$siteSubnetObj = New-Object PSCustomObject | Select SiteName, Subnets
$siteSubnetObj.SiteName = $siteObj.Name
$siteSubnetObj.Subnets = $subnetArray
$file = "C:\temp\1.csv"
Out-File $file -encoding ASCII -input $siteSubnetObj -append
}
I expect to pull out AD location details also using the script.
You can shorten this script by using the Get-ADReplicationSite command. I would also consider using Export-Csv since you are outputting objects to file.
Get-ADReplicationSite -Filter * -Properties Subnets,Location |
Select #{n='SiteName';e={$_.Name}},
#{n='Subnets';e={$_.Subnets -replace "^CN=(.*?),CN=Subnets,.*$",'$1'}},Location |
Export-Csv -Path 'C:\temp\1.csv' -encoding ASCII -NoTypeInformation
Export-Csv will create a comma delimited file by default (the delimiter is changeable) with the first line (headers) being the property names of your objects. Each other line will contain comma separated values for each of those properties. The columns for properties and values will line up perfectly.
If you have more than 4 subnets per site, the Out-File method alone without changing anything else will cut off the subnet values. You would need to set $formatenumerationlimit to something higher than 4 or -1 for unlimited or make sure the output is not in table format. It will be much harder to work with this file if you don't use Export-Csv because there will not be a consistent delimiter between item properties and their values.
I can add location details to this if you explain exactly what that is.
I do not know what are you referring to with "Location" so can't help there.
Also, I understand the file output is easier to read that way and is probably consumed by a human as a report but you have to consider Out-file will default to your screen width when redirecting to file so the number of subnets which will be saved will depend on that width (and not on a fixed value such as 4). To enlarge the output width you can use the -width parameter
$Something | out-file $file -width 600
or set the default width:
$PSDefaultParameterValues=#{"Out-File:Width"="600"}
Note that large numbers may have undesired side effects.
I have a list of IP addresses. They all start with 10.10. I want all the unique values of the third octet. This way I can count how many of that unique value there are.
10.10.26.251
10.10.27.221
10.10.26.55
10.10.31.12
10.10.12.31
10.10.31.11
10.10.27.15
10.10.26.5
When I am done I want to know that I have 3 .26 network devices, 2 27, and so on so forth. Other than breaking down the octet with a split and looping through each one, I can't think of any single liners. Any suggestions?
here's a small variant. [grin] i already had this before noticing the other answers - and it is a tad different.
what it does ...
creates a collection of IPv4 address objects to work with
groups them by a calculated property [the 3rd octet]
creates a [PSCustomObject] for each resulting group
sends it to the $Octet3_Report variable
shows it on screen
output to a CSV file would be easy at that point. here's the code ...
$IP_List = #(
[ipaddress]'10.10.26.251'
[ipaddress]'10.10.27.221'
[ipaddress]'10.10.26.55'
[ipaddress]'10.10.31.12'
[ipaddress]'10.10.12.31'
[ipaddress]'10.10.31.11'
[ipaddress]'10.10.27.15'
[ipaddress]'10.10.26.5'
)
$Octet3_Report = $IP_List |
Group-Object -Property {$_.ToString().Split('.')[2]} |
ForEach-Object {
[PSCustomObject]#{
Octet_3 = $_.Name
Count = $_.Count
}
}
$Octet3_Report
on screen output ...
Octet_3 Count
------- -----
26 3
27 2
31 2
12 1
It's like me to figure it out after the fact.
The Return contains the dns records. The IP address are stored inside recorddata. I pull the end of the IP address off. Then loop through grabbing only the range and count with a foreach loop to make it cleaner.
$DNSRecordCounts = #()
$Ranges = ($Return | where-object {$_.recorddata -like "10.10.*"}).recorddata -replace "\.\d{1,3}$" | select -Unique
foreach ($range in $Ranges) {
$DNSRecordCounts += [pscustomobject][ordered]#{
IPRange = $range
Count = ($Return | Where-Object {$_.recorddata -like "$($range).*"}).Count
}
}
Based on your question and what I can infer from your own answer, if you are looking for something a little more like "idiomatic" PowerShell you want the following:
$Return `
| Select-Object -ExpandProperty recorddata `
| ForEach-Object {
$_ -match "\d+\.\d+\.(?<octet>\d+)\.\d+" | Out-Null
$Matches.octet
} `
| Group-Object `
| ForEach-Object {
[PSCustomObject]#{
Octet = $_.Name
Count = $_.Count
}
}
So I am a complete beginner at Powershell but need to write a script that will take a file, compare it against another file, and tell me what strings are different in the first compared to the second. I have had a go at this but I am struggling with the outputs as my script will currently only tell me on which line things are different, but it also seems to count lines that are empty too.
To give some context for what I am trying to achieve, I would like to have a static file of known good Windows processes ($Authorized) and I want my script to pull a list of current running processes, filter by the process name column so to just pull the process name strings, then match anything over 1 character, sort the file by unique values and then compare it against $Authorized, plus finally either outputting the different process strings found in $Processes (to the ISE Output Pane) or just to output the different process names to a file.
I have spent today attempting the following in Powershell ISE and also Googling around to try and find solutions. I heard 'fc' is a better choice instead of Compare-Object but I could not get that to work. I have thus far managed to get it to work but the final part where it compares the two files it seems to compare line by line, for which would always give me false positives as the line position of the process names in the file supplied would change, furthermore I only want to see the changed process names, and not the line numbers which it is reporting ("The process at line 34 is an outlier" is what currently gets outputted).
I hope this makes sense, and any help on this would be very much appreciated.
Get-Process | Format-Table -Wrap -Autosize -Property ProcessName | Outfile c:\users\me\Desktop\Processes.txt
$Processes = 'c:\Users\me\Desktop\Processes.txt'
$Output_file = 'c:\Users\me\Desktop\Extracted.txt'
$Sorted = 'c:\Users\me\Desktop\Sorted.txt'
$Authorized = 'c:\Users\me\Desktop\Authorized.txt'
$regex = '.{1,}'
select-string -Path $Processes -Pattern $regex |% { $_.Matches } |% { $_.Value } > $Output_file
Get-Content $Output_file | Sort-Object -Unique > $Sorted
$dif = Compare-Object -ReferenceObject $(Get-Content $Sorted) -DifferenceObject $(get-content $Authorized) -IncludeEqual
$lineNumber = 1
foreach ($difference in $dif)
{
if ($difference.SideIndicator -ne "==")
{
Write-Output "The Process at Line $linenumber is an Outlier"
}
$lineNumber ++
}
Remove-Item c:\Users\me\Desktop\Processes.txt
Remove-Item c:\Users\me\Desktop\Extracted.txt
Write-Output "The Results are Stored in $Sorted"
From the length and complexity of your script, I feel like I'm missing something, but your description seems clear
Running process names:
$ProcessNames = #(Get-Process | Select-Object -ExpandProperty Name)
.. which aren't blank: $ProcessNames = $ProcessNames | Where-Object {$_ -ne ''}
List of authorised names from a file:
$AuthorizedNames = Get-Content 'c:\Users\me\Desktop\Authorized.txt'
Compare:
$UnAuthorizedNames = $ProcessNames | Where-Object { $_ -notin $AuthorizedNames }
optional output to file:
$UnAuthorizedNames | Set-Content out.txt
or in the shell:
#(gps).Name -ne '' |? { $_ -notin (gc authorized.txt) } | sc out.txt
1 2 3 4 5 6 7 8
1. #() forces something to be an array, even if it only returns one thing
2. gps is a default alias of Get-Process
3. using .Property on an array takes that property value from every item in the array
4. using an operator on an array filters the array by whether the items pass the test
5. ? is an alias of Where-Object
6. -notin tests if one item is not in a collection
7. gc is an alias of Get-Content
8. sc is an alias of Set-Content
You should use Set-Content instead of Out-File and > because it handles character encoding nicely, and they don't. And because Get-Content/Set-Content sounds like a memorable matched pair, and Get-Content/Out-File doesn't.
Working on a script to delete all but the highest "Copy" printer (Microsoft and its infinite helpfulness creates "Copy" printers every time one of my remote users unplugs/plugs in a printer) on Windows 7 PCs.
I have two different printer names which get many "copies" made due to this problem. In one case, it's easy because I want to delete all of the "Copy" printers, but leave the original printer - the one that doesn't have "Copy" in its name. I do that by first clearing all print jobs (will not delete the printer if there's an existing job sitting in the queue), then delete all the "POS Lexmark (Copy)" printers -
Get-WmiObject Win32_Printer | ForEach-Object {$_.CancelAllJobs()}
Get-WmiObject Win32_Printer -Filter "name LIKE '%POS Lexmark (Copy%'" | ForEach-Object {$_.Delete()}
Works great. In the second case, I want to keep the highest "Copy" number printer - i.e. if there are 12 "Copy" printers, I want to keep the "Lexmark Universal PS3 (Copy 12)" printer, but delete all the rest. I do have a natural sort function line:
$ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }
Which I can use to sort all the "Copy" printers in this case, but this
Get-WmiObject Win32_Printer -Filter "name LIKE '%PS3 (Copy%'" | Sort-Object $ToNatural | Select-Object | ForEach-Object {$_.Delete()}
won't work because I still need to keep whatever that highest number printer is after the sort. I'm a Powershell newbie, so any help would be appreciated since a Google search has not turned anything up for me yet.
Thank you
Could you place your sorted results into a variable, and select-object all but the last (highest) result?
$ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }
$sorted = Get-WmiObject Win32_Printer -Filter "name LIKE '%PS3 (Copy%'" | Sort-Object $ToNatural
$sorted | Select-Object -First ($sorted.Count-1) | ForEach-Object {$_.Delete()}