PowerShell Test-Connection -efficient Method - powershell

One Bronze medal = Newbi 😊, have scoured this site for years, so a million belated thanks, once again!
Just finished up a few different PowerShell ver 5.1 scripts that perform a test-connection on the same 2000 devices regularly.
Out to a csv file
And they are all slow, some minor speed improvements when tweaking it down to the bare bones.
I’ve scaled the script to the minimal, IPADDRESS and NAME only
I’ve run a few comparison tests against a batch file (ping 123.456.789.001 >> PingTest.txt ) and these tests APPEAR to run quicker than my PS script (will run a full apple to apples test tomorrow), but confident something is amiss in the PS code.
Am looking at arrays, and how piping works, but am afraid I’m putting the cart in front of the horse, or there is something going on with how PS handles the csv format and/or ping list in its memory
My most efficient current script below,
Is the following link pointing me in the right direction,? Specifically the asJob switch
PowerShell Mass Test-Connection
$info = "" | Select IPaddress,Name
$OutputFile = new-item -itemType File -path C:\Temp\Results.csv -force -value "IPaddress,Name`r`n" | out-null
Import-csv C:\Temp\GetList.csv | ForEach-Object {
if ($_.IPaddress) {
if (-Not (Test-Connection -ComputerName $_.IPaddress -Quiet -Count 2 -ErrorAction SilentlyContinue)) {
$info.IPaddress = $_.IPaddress
$info.Name = $_.Name
add-content -value "$($info.IPaddress),$($info.Name)" -path C:\Temp\Results.CSV
}
}
}
The export-csv is faster, thank you. I'm not sure if I have it in the right place, but it works, will run some tests on it tomorrow. I may have to include the passes tests though , still getting my head around the get-job cmdlet.
$info = "" | Select IPaddress,Name,Status
Import-csv c:\Temp\GetList.csv | ForEach-Object {
if($_.Ipaddress) {
if (-Not(Test-Connection -ComputerName $_.IPaddress -Quiet -Count 2 -ErrorAction SilentlyContinue)) {
$info.IPaddress = $_.IPaddress
$info.Name = $_.Name
$info.Status = "Failed"
$info | export-csv C:\Temp\Results1.csv -Force -Notypeinformation -Append
}
}
}
$list = Import-Csv C:\Temp\GetList.csv
ForEach ($item In $list) {
Start-Job -ScriptBlock {
param($item)
if (Test-Connection -Computername $item.IPaddress -Quiet -Count 1) {
Add-Content -value "$($item.IPaddress),$($item.Name),$($item.Stauts)" -Path C:\Temp\xlistlist.csv
$list | Export-csv c:\Temp\xCSvreults.csv -Force -NoTypeInformation -Append
}
} -argumentlist $list
}
Thanks Santiago, I did some exploring here, just ran out of time for the day. THANK YOU you for the code examples they cleared a few things up for me :-) –
Have Computer
Dec 2 at 3:59
Would you mind adding some inline comments so I can follow along? Just trynna understand multi-threading (using runspaces) a bit better. –
Abraham Zinala
Dec 2 at 4:58
1
#AbrahamZinala is there a specific part you're struggling to understand? i'm not an expert on this and wouldn't like to give false information. Might be better to pick up all the parts you don't understand and ask a new question –
Santiago Squarzon
Dec 2 at 12:19
Nah man, sorry, I figured it out lol –
Abraham Zinala
Dec 2 at 13:43
1
#AbrahamZinala the only hard part to understand is the [powershell] instance part imo, and you can think of it as a Start-Job but much faster and less memory consuming, you add your scriptblock with .AddScript({...}) and then the parameters with .AddParameters([hashtablehere]). It's important to save the PSInstance in a variable so that we can get the output out of it and it's handler (Status = $psinstance.BeginInvoke()) to know when the instance has completed. The rest is just pure blueprint, once you have one you can copy paste it :P –
Santiago Squarzon
Dec 2 at 13:50
#HaveComputer I have no idea what your comment means. –
Santiago Squarzon
Dec 5 at 1:10
the 2 solutions presented work much faster, my code however is lacking. –
Have Computer
Dec 5 at 1:16
#HaveComputer I know that, this can perform 50x faster or even more depending on hardware and bandwidth. –
Santiago Squarzon

This is a good place where you can use runspaces, I would love to test if Test-Connection -AsJob performs better than this but for some reason it is not available on PS Core on Linux.
Code below took around 10 seconds to scan 254 IPs using my private network IP range. There is a lot of tweaking that can be done, i.e. you can poke around the $Threshold variable, it's currently running 100 runspaces at a time, Count and TimeoutSeconds for Test-Connection have been set to 2 you can tweak that too.
$results variable can be exported using Export-Csv.
# Change this value for tweaking
$Threshold = 100
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $Threshold)
$RunspacePool.Open()
# This is for testing, use your CSV here instead
# => $list = Import-Csv C:\Temp\GetList.csv
$list = 1..254 | ForEach-Object {
[pscustomobject]#{
IPAddress = "192.168.1.$_"
Hostname = "ExampleHost$_"
}
}
$scriptBlock = {
param($ip, $hostname)
$params = #{
Quiet = $true
Count = 2
TimeoutSeconds = 2
ComputerName = $ip
}
$status = Test-Connection #params
[pscustomobject]#{
Hostname = $hostname
IPAddress = $ip
Status = ('Failed','Success')[[int]$status]
}
}
$runspaces = foreach($line in $list)
{
$params = #{
ip = $line.IPAddress
hostname = $line.Hostname
}
$psinstance = [powershell]::Create().AddScript($scriptBlock).AddParameters($params)
$psinstance.RunspacePool = $RunspacePool
[pscustomobject]#{
Instance = $psinstance
Status = $psinstance.BeginInvoke()
}
}
while($runspaces.Status.IsCompleted -contains $false)
{
Start-Sleep -Milliseconds 500
}
$results = $runspaces.ForEach({ $_.Instance.EndInvoke($_.Status) })
$RunspacePool.Dispose()
$results Sample:
Hostname IPAddress Status
-------- --------- ------
ExampleHost1 192.168.1.1 Success
ExampleHost2 192.168.1.2 Failed
ExampleHost3 192.168.1.3 Success
ExampleHost4 192.168.1.4 Failed
ExampleHost5 192.168.1.5 Success
ExampleHost6 192.168.1.6 Failed
ExampleHost7 192.168.1.7 Failed
ExampleHost8 192.168.1.8 Failed
ExampleHost9 192.168.1.9 Failed
ExampleHost10 192.168.1.10 Failed
...
...

Start-Job -ScriptBlock { Test-Connection -computername (Get-Content -Path “C:\Temp\GetList.csv”) }
Its lacking the fundamentals, works, DE fast.

Related

PowerShell-script with invoke-command gets stuck on specific computers

I'm writing a PowerShell-Script that reads all shares from all AD-Servers and outputs them into a csv-file. At the same time the script is saving all occuring errors and outputs them into an error-log. The script will be run as a weekly task.
When I run the script, all goes well until it gets to one specific server on which it just loads infinitely. The AD-Server (Windows Server_2012_R2) is inside a normal OU and other servers in this OU work fine.
I believe that there are a few others on which the problem occures as well but I'm not sure how many exactly (about one in 150).
It does not produce an error.
Do you know what could be the Problem here?
Code:
$ErrorActionPreference = 'Continue'
$computers = (Get-Content C:\PowerShell\Shares\serverlist.txt).ForEach({
if(-not [string]::IsNullOrWhiteSpace($_))
{
"$_.domain.com"
}
})
$remoteCode = {
Get-SmbShare | Where-Object Path | Get-Acl |
Select-Object -Property "PSChildName", "Path", "Group", "AccessToString"
}
$results = Invoke-Command -ComputerName $computers -ScriptBlock $remoteCode 2>&1
$errors, $good = $results.Where({$_ -is [System.Management.Automation.ErrorRecord]}, 'Split')
$good | Sort-Object PSComputerName | Select-Object "PSComputerName", "PSChildName", "Path", "Group", #{ Name = "AccessToString"; Expression = { $_.AccessToString -replace("268435456", "FullControl") -replace("-1610612736", "ReadAndExecute")}} | export-csv -path C:\PowerShell\Shares\shares.csv -NoTypeInformation -delimiter ";"
$errors.Exception.Message | Set-Content $error_logfile -Encoding Unicode
Alright I now found the problem. Two servers where frozen and that resulted in the never ending PowerShell script. Now I think I will configure some Sort of timeout, does anyone of you know how I could do that the best way?
Thank you all for your help.

Using Get-Job to test-connection, but quite different [duplicate]

This question already has answers here:
If using Test-Connection on multiple computers with -Quiet how do I know which result is for which computer?
(2 answers)
Closed 2 years ago.
It's my first post here, I'm tring to write scripts on PS on my own, now my target is to write script that checks if computer is online at network, for example: test-Connection 192.168.0.1, 2, 3 etc. Doing this one by one on loop for takes some time if some computers are offline, I've found some tutorials on this site to use -AsJob param, but I'm not really Sure how could it work. I mean I'd like to output every checked PC to excel, so i need if operator. eg:
if (Job1 completed successfull (computer pings)){
do smth}...
I need to get output from Job boolean (true/false), but one by one. I'm taking my first steps in PS, I've made program that checks it one by one in for loop, but as i said it take some time till my excel file fill...
I can see, that AsJob makes working more effective and I think it's important to understand it
Thanks and sorry for bad text formatting, by the time I'll go on with this!
In your example, in the Start-Job scriptblock you are trying to access $_ which is not available in the codeblock scope. If you replace $_ with $args[0] it should work since you are passing in the $ip value as an argument
Your Example
$ipki = Get-Content 'C:\Users\pchor\Desktop\ipki.txt'
foreach ($ip in $ipki) {
Start-Job -Name "$ip" -ScriptBlock {
Test-Connection $_ -Count 1 # <---- replace $_ with $args[0]
} -ArgumentList $_ # <----- change $_ to $ip
}
You'll probably also want to wait for all the jobs to finish. I recommend something like this
$computers = #(
'www.google.com'
'www.yahoo.com'
)
$jobs = $computers |
ForEach-Object {
Start-Job -ScriptBlock {
[pscustomobject]#{
Computer = $using:_
Alive = Test-Connection $using:_ -Count 1 -Quiet
}
}
}
# Loop until all jobs have stopped running
While ($jobs |
Where-Object { $_.state -eq 'Running' }) {
"# of jobs still running $( ($jobs | Where-Object {$_.state -eq 'Running'}).Count )";
Start-Sleep -Seconds 2
}
$results = $jobs | Receive-Job | Select-Object Computer, Alive
$results | Format-Table
Output
Computer Alive
-------- -----
www.google.com True
www.yahoo.com True
To modify the properties to what you want there are different ways of doing this. Easiest in this case is probably to use a calculated property
$newResults = $results |
Select-Object Computer,
#{Label = 'State'; Expression = { if ($_.Alive) { 'Online' } else { 'Offline' } } }
Objects will now look like this (I added another fake address to illustrate offline state)
Computer State
-------- -----
www.google.com Online
www.yahoo.com Online
xxx.NotAValidAddress.xxx Offline
You can then export the objects to csv using Export-csv
$newResults | Export-Csv -Path c:\temp\output.csv

Duplicates in Workflow VM audit

I'm implementing a VM audit of several vcenters (around 5 of them) where the report is simply a csv of each VM along with a few properties. Although this script would run overnight, I found that it took around 5-6 hours to complete and wanted to increase its efficiency. I learned about workflows and figured it would be faster to audit each vcenter at the same time instead of one by one. It was slower than I expected finishing after about 4 hours. I noticed that there were many duplicates in the data and I can't figure out why that would be; maybe my ideas about how workflow works is flawed. I'm also looking for any tips on raising efficiency in my code. Thanks in advance.
The workflow:
workflow test {
param([string[]]$vcenters, [string]$session, [string]$username, [string]$password)
foreach -parallel($vcenter in $vcenters){
$main = InlineScript{
Add-PSSnapin VMware.VimAutomation.Core
Connect-VIServer -Server $Using:vcenter -User $Using:username -Password $Using:password
$vms = Get-View -ViewType VirtualMachine -Property Name, Summary.Config.GuestFullName, Runtime.Powerstate, Config.Hardware.MemoryMB, Config.Hardware.NumCPU
ForEach($machine in $vms){
$vm = Get-VM -Server $Using:vcenter -Name $machine.Name -ErrorAction SilentlyContinue
$object = New-Object -Type PSObject -Property ([ordered]#{
Name = $machine.Name
GuestOS = $machine.Summary.Config.GuestFullName
PowerState = $machine.Runtime.PowerState
MemoryGB = ($machine.Config.Hardware.MemoryMB / 1024)
CPU = $machine.Config.Hardware.NumCPU
VLAN=(Get-NetworkAdapter -VM $vm |Sort-Object NetworkName |Select -Unique -Expand NetworkName) -join '; '
})
$object| Export-Csv -Append “C:\TestReports\$($vcenter)_TestReport.csv” -NoTypeInformation
}
Disconnect-VIServer - $Using:vcenter -Confirm:$false
}
}
}
With the below changes, maybe it runs quickly enough that you no longer need parallelism or workflow. Not sure if those elements are a cause of the duplication problem. If not, you might need to share more details from your environment for help with that piece.
Get-VM is slow. You're calling it once for each VM, and I don't think you need it at all. Try adding this line after connecting to vCenter
$networks = Get-View -ViewType Network
Replacing your VLAN= line with
VLAN= $networks | ? {$_.VM.Value -contains $machine.MoRef.value} | select -exp Name
And dropping your $vm = Get-VM... line entirely.

Powershell script: create loop for ResponseTime

I am having an issue with the way that my ping results "roll" out on the screen. I am using this code:
$servers = "192.168.2.10","192.168.2.80","192.168.2.254"
$collection = $()
foreach ($server in $servers)
{
$status = #{ "ServerName" = $server; "TimeStamp" = (Get-Date -f s) }
$testconnection = (Test-Connection $server -Count 1 -ea 0)
$response = ($testconnection | select ResponseTime)
if ($response)
{
$status["Results"] = "Up"
$status["Responsetime"] = $response
}
else
{
$status["Results"] = "Down"
}
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus
$collection += $serverStatus
}
$collection | Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
I would to like create a loop for the ResponseTime
The code that I am using now gives one response.
When I give a count of 2, it prints the ResponseTime next to eachother per IP-adres.
Output:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:30:17 {#{ResponseTime=6}, #{ResponseTime=4}} Up 192.168.2.10
2014-10-22T23:30:18 Down 192.168.2.80
2014-10-22T23:30:25 {#{ResponseTime=1}, #{ResponseTime=3}} Up 192.168.2.254
What I want is, that the script prints each ResponseTime under eachother like this:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:11:50 #{ResponseTime=419} Up 192.168.2.10
2014-10-22T23:11:51 #{ResponseTime=415} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:52 #{ResponseTime=470} Up 192.168.2.254
2014-10-22T23:11:52 #{ResponseTime=7} Up 192.168.2.254
Or like this:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:11:50 #{ResponseTime=419} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:51 #{ResponseTime=415} Up 192.168.2.254
2014-10-22T23:11:52 #{ResponseTime=470} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:52 #{ResponseTime=7} Up 192.168.2.254
It doesn't matter which one, my preference is the second one
Could you please help me with this matter. Even if it is not possible tell me aswell.
Thank you,
Chris
I'll chime in late, not because the other answer are wrong by any means, they are both functional, but more so because nobody has pointed out that you are recreating the wheel.
You test the connection, and specify an erroraction for it that silently continues leaving your variable null. Then you have to test to see if the variable has results, and treat it one way, or if it doesn't treat it another way. What you have just done is made your own Try/Catch scenario. If you actually use the error to stop you can use the built in Try/Catch. Consider this approach:
$servers = "www.google.com","localhost","www.amazon.com"
$collection = #()
foreach ($server in $servers)
{
Try{
$testconnection = Test-Connection $server -Count 2 -ErrorAction Stop
$testconnection | ForEach{$collection += New-Object PSObject -Property ([ordered]#{
'TimeStamp' = Get-Date -Format s
'Server' = $server
'ResponseTime' = $_.responsetime
'Results' = 'Up'})
}
}
Catch{
$collection += New-Object PSObject -Property ([ordered]#{
'TimeStamp' = Get-Date -Format s
'Server' = $server
'ResponseTime' = $null
'Results' = 'Unreachable'
})
}
}
$collection #| Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
That tries to ping the server, and if it can it adds a custom object to the $collection array with the desired information. If the ping fails it also adds an object to the $collection showing that the server was unreachable.
Also, you had $collection = $(). I assume you were trying to create an empty array, which is correctly done $collection = #() (corrected in my suggested code). Now, I commented out the Export-CSV so I could see the results. This is what I saw:
TimeStamp Server ResponseTime Results
--------- ------ ------------ -------
2014-10-22T17:54:22 www.google.com 9 Up
2014-10-22T17:54:22 www.google.com 12 Up
2014-10-22T17:54:23 localhost 0 Up
2014-10-22T17:54:23 localhost 0 Up
2014-10-22T17:54:27 www.amazon.com Unreachable
Amazon didn't let me ping it, so it shows as unreachable.
Moving on to why your desired results are not practical... What you describe shows you pinging your servers and getting results from them at non-consecutive times. To do that you would have to do -count 1, and loop through the ForEach loop twice, so it would ping server 1 for 1 result, then server 2 for 1 result, then server 3 for 1 result. Then it would go back and ping server 1 for a second result, then server 2 for a second result, and then server 3 for a second result. If you wanted to do that you could I suppose, and it should give you your desired results, you would have to do something like this:
$servers = "www.google.com","localhost","www.amazon.com"
$collection = #()
$count = 2
for($i=1;$i -le $count;$i++){
ForEach($server in $servers){
do stuff to ping servers as described above, except change -count to 1
}
}
$collection | export-CSV '.\ServerStatus.csv' -notype
That will give you your desired results, but it is slower. If you have to run this against more than a few servers it will be noticeably slower. For just those three servers listed it made the entire process go from taking 3.7240945 seconds to taking 7.6104075 seconds (roughly double).
Instead of
$response = ($testconnection | select ResponseTime)
if ($response)
{
$status["Results"] = "Up"
$status["Responsetime"] = $response
}
do
if($testconnection)
{
$testconnection | % {
$status = #{"ServerName" = $server; "TimeStamp" = (Get-Date -f s); "Results" = "Up"; "Responsetime"= $_.responsetime};
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus;
$collection += $serverStatus }
}
else
{
$status = #{"ServerName" = $server; "TimeStamp" = (Get-Date -f s); "Results" = "Down"};
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus;
$collection += $serverStatus
}
The problem is that $testconnection or in your case $response is an array if the count of Test-Connection is greater then 1, so you have to loop through it and add the single entries to your collection.
Also to get the Value instead of the gibberish you get you have to call the .responsetime property.
In hopes I didn't make it too complicated I present this solution
$servers = "10.50.10.100","8.8.8.8","169.254.54.1"
$servers | ForEach-Object{
$server = $_
$timeStamp = (Get-Date -f s)
$testconnection = Test-Connection $server -Count 2 -ErrorAction 0
If(!$testconnection){
$props = #{
Server = $server
TimeStamp = $timeStamp
ResponseTime = ""
Results = "Down"
}
New-Object -TypeName PSObject -Property $props
} Else {
$testconnection | ForEach-Object{
$_ | Select-Object #{l='Server';e={$server}},#{l='TimeStamp';e={$timeStamp}},#{l='ResponseTime';e={$_.ResponseTime}},#{l='Results';e={"Up"}}
}
}
} | Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
So your logic is still here but as you can see some things have been changed. Paul was right, in that you needed to loop for each ResponseTime element you had. I also have done that but with a different approach that, if nothing else, will show you some of the Power in PowerShell. A break down of the code
Pipe $servers into a ForEach-Object. ForEach in works fine however I wanted to skip the saving the variables and just output straight to Export-CSV which is why I changed it.
So if you use Test-Connection on a server that does not exist or errors for some reason then you need to create an object to represent that. Using the desired properties, build a object with required values. This is output to the pipe instead of using a temporary variable.
When a connection test is successful then we need to output a number or variables to match the number of returns.
Continuing from #3 we use Select-Object to output the desired values. l stand for label and e for expression. Yes you could easily just use another $props variable. Just illustrating another option.
Since we changed the ForEach in the first step we can just output straight to Export-CSV
Sample output
Server TimeStamp ResponseTime Results
------ --------- ------------ -------
10.50.10.100 2014-10-22T20:22:01 0 Up
10.50.10.100 2014-10-22T20:22:01 0 Up
8.8.8.8 2014-10-22T20:22:02 43 Up
8.8.8.8 2014-10-22T20:22:02 39 Up
169.254.54.1 2014-10-22T20:22:03 Down

Verifying system partition alignment via scripting

I'm trying to verify that the file system partitions within each of the servers I'm working on are aligned correctly. I've got the following script that when I've tried running will either claim that all virtual servers are aligned or not aligned based on which if statement I use (one is commented out):
$myArr = #()
$vms = get-vm | where {$_.PowerState -eq "PoweredOn" -and $_.Guest.OSFullName -match "Microsoft Windows*" } | sort name
foreach($vm in $vms){
$wmi = get-wmiobject -class "win32_DiskPartition" -namespace "root\CIMV2" -ComputerName $vm
foreach ($partition in $wmi){
$Details = "" | Select-Object VMName, Partition, Status
#if (($partition.startingoffset % 65536) -isnot [decimal]){
if ($partition.startingoffSet -eq "65536"){
$Details.VMName = $partition.SystemName
$Details.Partition = $partition.Name
$Details.Status = "Partition aligned"
}
else{
$Details.VMName = $partition.SystemName
$Details.Partition = $partition.Name
$Details.Status = "Partition not aligned"
}
$myArr += $Details
}
}
$myArr | Export-CSV -NoTypeInformation "C:\users\a411882\Documents\Scripts\PartitionAlignment.csv"
Would anyone know what is wrong with my code? I'm still learning about partitions so I'm not sure how I need to check the starting off-set number to verify alignment.
You're passing a virtual machine object instead of a string to get-wmiObject -ComputerName. When I do that, get-wmiObject throws an RPC error. You might try -computerName $vm.guest.Hostname instead of -computerName $vm.
In the commented line, your use of % should return a remainder, which will always be a whole number or zero. Maybe you were expecting a quotient instead, and wanted to evaluate if it's an integer?
PS C:\temp> (1 / 2) -isnot [int]
True
PS C:\temp> (2 / 1) -isnot [int]
False
Recent Windows OS align their partitions automatically, so there's that. Here's a good post about alignment generally on VMware, including a link to a more detailed discussion of guest partitions.