Failover cluster Alert Automation - arrays

In a high-availability environment (two DCs, Primary and Standby) with 50 clusters. Each cluster has 5 to 6 nodes in it. I want to ensure all the nodes in every cluster is "Online" (State) and running in "Primary" (OwnerNode). Any node that shows otherwise has to be noted.
I'm using arrays to store the required information in a foreach loop. The problem is, it is taking too long to compile. I want to complete it sooner.
$Clusternodes= * some 50 elements *
$Standbynodes= * some 50 elements *
foreach ($cluster in $Clusternodes) {
$NotOnline += Get-ClusterGroup -Cluster $Cluster |
where {$_.State -ne "Online"} |
Select-Object Name,OwnerNode,State
foreach ($node in $Standbynodes) {
$Standbys += Get-ClusterGroup -Cluster $Cluster |
where {$_.OwnerNode -eq "$node"} |
Select-Object Name,OwnerNode,State
}
}
Edit:
Get-clustergroup -Cluster $Cluster returns 5 to 6 entries in every cluster. The output contains three columns Name, OwnerNode, State.
I'm storing every entry in an array based on its state (whether it is running or not) and owner node (whether in primary or secondary DC). Hence, I want four arrays. While the former is easy, the latter isn't. As that itself is another two arrays with 50 odd elements in each. Hence I used hashtables for it as mentioned below. However, when I tried the below code it always returns an empty array.
$Clusternodes = * some 50 elements *
$Standbynodes = * some 50 elements *
$Primarynodes = * some 50 elements *
$pr = #{}
$sb = #{}
$Standbynodes | ForEach-Object { $sb[$_] = $true }
$Primarynodes | ForEach-Object { $pr[$_] = $true }
$RunninginPrimary = #()
$NotRunninginPrimary = #()
$RunninginStandby = #()
$NotRunninginStandby = #()
foreach ($cluster in $Clusternodes) {
$c = Get-ClusterGroup -Cluster $Cluster
$NotRunninginStandby += $c | Where-Object {
($_.State -ne "Online") -and ($sb.ContainsKey($_.OwnerNode))
} | Select-Object Name,OwnerNode,State
$NotRunninginPrimary += $c | Where-Object {
($_.State -ne "Online") -and ($pr.ContainsKey($_.OwnerNode))
} | Select-Object Name,OwnerNode,State
$RunninginStandby += $c | Where-Object {
($_.State -eq "Online") -and ($sb.ContainsKey($_.OwnerNode))
} | Select-Object Name,OwnerNode,State
$RunninginPrimary += $c | Where-Object {
($_.State -eq "Online") -and ($pr.ContainsKey($_.OwnerNode))
} | Select-Object Name,OwnerNode,State
}

You query each cluster multiple times. To speed up your code query each cluster just once, store the result in a variable, and use that variable in the rest of the loop. You may also want to replace the nested loop that iterates over $Standbynodes with a hashtable lookup.
$Clusternodes = ...
$Standbynodes = ...
$sb = #{}
$Standbynodes | ForEach-Object { $sb[$_] = $true }
$NotOnline = #()
$Standbys = #()
foreach ($cluster in $Clusternodes) {
$c = Get-ClusterGroup -Cluster $Cluster
$NotOnline += $c | Where-Object { $_.State -ne "Online" } |
Select-Object Name,OwnerNode,State
$Standbys += $c | Where-Object { $sb.ContainsKey($_.OwnerNode) } |
Select-Object Name,OwnerNode,State
}

Try using workflow.
workflow clusterCheck {
$Clusternodes= * some 50 elements *
$Standbynodes= * some 50 elements *
foreach -parallel ($cluster in $Clusternodes) {
$NotOnline += Get-ClusterGroup -Cluster $Cluster |
where {$_.State -ne "Online"} |
Select-Object Name,OwnerNode,State
foreach -parallel ($node in $Standbynodes) {
$Standbys += Get-ClusterGroup -Cluster $Cluster |
where {$_.OwnerNode -eq "$node"} |
Select-Object Name,OwnerNode,State
}
}
}
Source:- Scripting guy

Related

Array causing 'system.outofmemoryexception'

I am running the below script and it is causing exception of type 'system.outofmemoryexception' was thrown
I believe that it is due to the #Results array growing past the 2gb allocated to windows shell. Is there possibly a way to iterate through the results, or am I stuck with allocating more memory (which could ultimately be a lot...)?
$Path = "path to output"
### Get all PF
$publicFolders = Get-PublicFolder "Public Folder Name" -Recurse -resultsize unlimited | Select-Object *
### Array to contain results
$results = #()
###Begin looping through each PF and grab each user/group with AccessRights to that folder
$final = ForEach($pf in $publicFolders){
$perms = Get-PublicFolderClientPermission -Identity $pf.Identity | Where-Object {$_.User -notmatch "Default|Anonymous|S-X-X-XX"}
Foreach($perm in $perms){
$temp = [PSCustomObject]#{
MailFolderName = $pf.Identity
UserWithPerms = $perm.User
AccessRights = $perm | Select-Object -ExpandProperty AccessRights
}
$results += $temp
}
}
$final | Export-Csv $path -NoTypeInformation
Am I barking up the wrong tree?
Thanks in advance.
Use the ForEach-Object cmdlet instead of a foreach(){} loop statement for the outer loop - this way you can start piping the output to Export-Csv immediately instead of buffering it in an array:
$publicFolders |ForEach-Object {
$pf = $_
$perms = Get-PublicFolderClientPermission -Identity $pf.Identity | Where-Object {$_.User -notmatch "Default|Anonymous|S-X-X-XX"}
Foreach($perm in $perms){
[PSCustomObject]#{
MailFolderName = $pf.Identity
UserWithPerms = $perm.User
AccessRights = $perm | Select-Object -ExpandProperty AccessRights
}
}
} | Export-Csv $path -NoTypeInformation
Alternatively, flush the partial set of results to file after enumerating the permissions for each folder:
ForEach($pf in $publicFolders){
$perms = Get-PublicFolderClientPermission -Identity $pf.Identity | Where-Object {$_.User -notmatch "Default|Anonymous|S-X-X-XX"}
$results = Foreach($perm in $perms){
[PSCustomObject]#{
MailFolderName = $pf.Identity
UserWithPerms = $perm.User
AccessRights = $perm | Select-Object -ExpandProperty AccessRights
}
}
# flush the buffer before moving to the next set of permissions
$results |Export-Csv $path -NoTypeInformation -Append
}

Powershell summing similarly named array propertys

Is there a way of summing similar property names in an array rather than doing this?
foreach ($row in $ExpenseData)
{
if ($row.refuel1_cost + $row.refuel2_cost + $row.refuel3_cost -gt 0)
{
}
}
If you want that sum for each rows, calculated properties are the perfect fit.
$ExpenseData | Select Name,#{'name'='Total refuel cost';'expression'={$_.refuel1_cost + $_.refuel2_cost + $_.refuel3_cost}}
I used the sample below to generate my expense data.
function CreateSamplePump($Name,$cost1,$cost2,$cost3){
$Props = #{'Name'='';'refuel1_cost'='';'refuel2_cost'='';'refuel3_cost'=''}
$Obj = New-Object psobject -Property $props
$obj.Name = $Name
$obj.refuel1_cost = $cost1
$obj.refuel2_cost = $cost2
$obj.refuel3_cost = $cost3
return $Obj
}
# Generating a sample for test purposes
$ExpenseData = New-Object System.Collections.ArrayList
$ExpenseData.Add((CreateSamplePump -Name 'Pump1' -cost1 10 -cost2 13 -cost3 4))
$ExpenseData.Add((CreateSamplePump -Name 'Pump2' -cost1 4 -cost2 2 -cost3 3))
$ExpenseData.Add((CreateSamplePump -Name 'Pump3' -cost1 3 -cost2 2 -cost3 1))
$ExpenseData.Add((CreateSamplePump -Name 'Pump4' -cost1 4 -cost2 8 -cost3 2))
$ExpenseData.Add((CreateSamplePump -Name 'Pump6' -cost1 6 -cost2 5 -cost3 1))
Edit:
In the event less likely event you would like to sum similar property names without knowing the number of refuel costs column in your dataset prior obtaining it, you could use something like that instead to get all similar names.
(Although if you have the names, use the name instead. It will be more efficient)
$CostsProperties = $ExpenseData | get-member | where -like -Property Name -Value 'refuel*_cost' | select -ExpandProperty Name
$ExpenseData | Select Name, #{'name'='Total refuel cost';'expression'={$Total = 0;Foreach ($c in $CostsProperties) {$Total += $_."$c"};return $Total }}
try this
$a=#{"refuel1_cost"=1;"refuel2_cost"=2;"refuel3_cost"=3;"something"=10}
$a.GetEnumerator() | ?{$_.name -match "refuel" } | select -expand value |measure -Sum |select -expand sum
Key to understanding my issue was dealing with a System.Data object - this post highlights some of the issues.
Sage's line of code $CostsProperties = $ExpenseData | get-member | where -like -Property Name -Value 'refuel*_cost' | select -ExpandProperty Name simplifies the script no end - thank you for that.
Below is the shell of my script
$ExpenseData = Get-MIS-Data -query $ExpensesQuery
# Get all the wildcard columns
$RefuelCols = $ExpenseData | get-member | where -like -Property Name -Value 'refuel*_cost' | select -ExpandProperty Name
#Check if any rows were returned
if ($ExpenseData.count -gt 0)
{
#Loop each returned row
foreach ($row in $ExpenseData)
{
foreach ($c in $RefuelCols)
{
$Total += $row.$c
}
if ($Total -gt 0)
{
#Knowing we have values do some useful stuff here
}
#Reset $Total before next loop
$Total = 0
}
}

powershell pipe to array returns as character array

I am trying to build an array with the results of the following powershell script. I would then like to only output the unique results in that array (I am expecting duplicates). I tried but it does not work. One thing I noticed is that my array is being converted to a character array. So if I call a single object in the array ($array[1]) it only displays one character, which may be why the select -unique is not working.
Please help.
$servers="Server1","server2"
$regex="(\.\d\d\d.*\()"
$path = "SomePath"
$yesterday = (get-date).Date.AddDays(-1)
Foreach ($server in $servers) {Get-ChildItem -Path $path -recurse | where { $_.LastWriteTime -le $yesterday } | ForEach-Object { Get-Content -Path "$_" -TotalCount 2 | select-string $regex | % { $_.Matches } | % { $array += $_.Value.Substring(5,6) } }}
$array|select -Unique
If you initialize the $array value as an array, then your code will work. Without this knowledge, PowerShell is treating $array += like concatenating strings together.
$array = #()
$servers="Server1","server2"
$regex="(\.\d\d\d.*\()"
$path = "SomePath"
$yesterday = (get-date).Date.AddDays(-1)
Foreach ($server in $servers) {Get-ChildItem -Path $path -recurse | where { $_.LastWriteTime -le $yesterday } | ForEach-Object { Get-Content -Path "$_" -TotalCount 2 | select-string $regex | % { $_.Matches } | % { $array += $_.Value.Substring(5,6) } }}
$array|select -Unique

Reference Elements in PowerShell Multidimensional Array

I have CSV file
My PowerShell Script attempts to store SourceIP, DestinationIP, and Traffic in multidimensional array
$source = #((Import-Csv D:\Script\my.csv).SourceIP)
$dest = #((Import-Csv D:\Script\my.csv).DestinationIP)
$t = #((Import-Csv D:\Script\my.csv).Traffic)
$multi = #($source),#($dest),#($t)
When I try to read from first element of $multi, I expect to get a list of SourceIP
foreach ($q in $multi){
write-host $q[0]
write-host `n
}
But instead, I get SourceIP, DestinationIP, Traffic, i.e.
10.153.128.110
10.251.68.80
3.66 GB
And if I try
foreach ($q in $multi){
write-host $q[0][0][0]
write-host `n
}
I get
1
1
3
How to troubleshoot?
UPDATE
Ultimate goal is to
Count total traffic
Count traffic if SourceIP or Destination IP fits into certain pattern, i.e. 10.251.22.x
Get percentage
UPDATE II
I am able to get code to import CSV and tally total bandwidth only, but I also need bandwidth from SourceIP and DestinationIP with certain pattern.
$t = #((Import-Csv D:\Script\my.csv).Traffic)
foreach ($k in $t){
write-host $k
}
foreach ($i in $t){
$j += ,#($i.split(" "))
}
foreach ($m in $j){
switch ($m[1]){
GB {
$m[0] = [int]($m[0]) * 1000
$m[1] = 'MB'
}
MB {}
KB {
$m[0] = [int]($m[0]) / 1000
$m[1] = 'MB'
}
}
$total_bandwidth += $m[0]
}
write-host Total bandwidth is ("{0:N2}" -f $total_bandwidth) MB
You should not split array of object to multiple parallel arrays of properties. It is much easy to operate when objects are whole.
$Scale=#{
B=1e00
KB=1e03
MB=1e06
GB=1e09
TB=1e12
}
$TrafficBytes={
$a=-split$_.Traffic
[double]$a[0]*$Scale[$a[1]]
}
Import-Csv D:\Script\my.csv|
ForEach-Object $TrafficBytes|
Measure-Object -Sum #total traffic
Import-Csv D:\Script\my.csv|
Where-Object {$_.DestinationIP-like'10.*'}| #condition
ForEach-Object $TrafficBytes|
Measure-Object -Sum #traffic by condition
PetSerAl has a good idea for the conversion, but here is a way to do this that requires iterating the CSV only once and will give your percentages.
$filter = "10.251.22.*"
$Scale=#{
B=1e00
KB=1e03
MB=1e06
GB=1e09
TB=1e12
}
$myCsv = Import-Csv D:\Script\my.csv | Select-Object *, #{ Name = "TrafficBytes"; Expression = { $a = -split $_.Traffic; [double] $a[0] * $Scale[$a[1]] } }
$trafficFiltered = $myCsv | Group-Object { $_.SourceIP -like $filter -or $_.DestinationIP -like $filter } | Select-Object #{ Name = "IPFilter"; Expression = { if ($_.Name -eq $true) { $filter } else { "Other" } } }, #{ Name = "TrafficBytes"; Expression = { ($_.Group | Measure-Object -Sum "TrafficBytes").Sum } }
$trafficTotal = $myCsv | Measure-Object -Sum TrafficBytes
$trafficReport = Select-Object IPFilter, TrafficBytes, #{ Name = "Percent"; Expression = { "{0:P}" -f $_.TrafficBytes / $trafficTotal.Sum * 100.0 } }
$trafficReport

Compare Patch Levels across Multiple servers

Hi all I was working on a script to compare a list of patch levels between multiple servers and show the list of patches missing on either of server. The script should compare between each server within the array and give the output i was trying using Get-Hotfix and also using compare-object to compare and get the server name evaluating $_.sideindicator -match "=>" and $_.sideindicator -match "<=".
can anyone please help further?
here's the code till now for four servers, if there are n number of servers i wanted the logic on how to proceed.
$array=#()
$serd1 = Get-HotFix -ComputerName serd1 | select -ExpandProperty hotfixid
$serd2 = Get-HotFix -ComputerName serd2 | select -ExpandProperty hotfixid
$serd3 = Get-HotFix -ComputerName serd3 | select -ExpandProperty hotfixid
$serd4 = Get-HotFix -ComputerName serd4 | select -ExpandProperty hotfixid
$check1 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd2 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check1 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check1 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$check2 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd3 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check2 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check2 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$check3 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check3 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check3 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$check4 = Compare-Object -ReferenceObject $serd2 -DifferenceObject $serd3 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check4 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check4 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$check5 = Compare-Object -ReferenceObject $serd2 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check5 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check5 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$check6 = Compare-Object -ReferenceObject $serd3 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check6 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check6 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$array
The question is how to make this script work for random number of servers
This question is old but thought this might help out if anyone else finds this and thinks it is something they'd want to give a go.
Anyway, to meet the requirements stated here, see if this helps - let me know if anyone finds problems:
$Servers = "SERVER1", "SERVER2", "SERVER3", "SERVER4"
$MissingPatches=#()
$ServerPatches = New-Object 'object[]' $($Servers.Length)
for($i=0; $i -lt $($Servers.Length); $i++)
{
$ServerPatches[$i] = Get-HotFix -ComputerName $Servers[$i] | select -ExpandProperty hotfixid
}
for($i=0; $i -lt $($Servers.Length); $i++)
{
for($j=($i+1); $j -lt $($Servers.Length); $j++)
{
$Compare = Compare-Object -ReferenceObject $ServerPatches[$i] -DifferenceObject $ServerPatches[$j] -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$MissingPatches += $Compare | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={$Servers[$i]}},#{l="MissingPatches";e={$_.inputobject}}
$MissingPatches += $Compare | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={$Servers[$j]}},#{l="MissingPatches";e={$_.inputobject}}
}
}
$MissingPatches = foreach($MissingPatch in $MissingPatches) {
if($($MissingPatch.MissingPatches) -ne "File 1")
{
$MissingPatch
}
}
$MissingPatches | ft -AutoSize
Going down this path I started thinking of potential improvements. So, if I have time I might rewrite and create a patch object that indicates on what server it is present and where it is missing for more detail of why a patch is indicated as "Missing".
When a patch is found on any server it is added to that array. A property of PresentOn will be updated with the server name it was found and the servers so far that didn't contain the patch can be added to a property of MissingOn, then checked continuously going forward. Then, a table of all patches can be displayed by these statuses vs. printing out only a "Missing" array. Could take some doing but might be worth the work. I'm also seeing this being a potentially costly script based on the number of patches in play and servers. But, if anyone else gets to it first (or finds flaws in the approach) let me know here!
This should work:
$servers = "one", "two", "three"
$array = #()
for ($i = 0; $i -lt ($servers.Count - 1); $i++)
{
$serd1 = Get-HotFix -ComputerName $servers[$i] | select -ExpandProperty hotfixid
$serd2 = Get-HotFix -ComputerName $servers[$i+1] | select -ExpandProperty hotfixid
$array += $check1 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check1 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
}
$array

Resources