New Member with my first question

I apologize for the vague subject, but I really did not know a good short subject to cover my question.

I am new to powershell and brand new to these forums, and I figured I would ask for some help. Rather than go all through what I am having problems with (Multiple aspects of my script), I will tell you what I want to do, and show you my half finished script with my problem areas I am stuck on highlighted, and I am sure you guys will probably be able to give me a 10 line code that will do what my 1000 line script would of done haha.

Task: Test-Connection multiple Servers from a text file (I can load this and do the for-each) but instead of simply returning a yes or no, I want to take that and either start or check a timer for each server, so I do not count it as bad until it has been down for a configurable amount of time ($AllowedDownTime) before an email notification is sent. I also have a Location for each Server as well, for logging purposes, and the two are loaded into arrays in the same statement so that the index is the definition of the array.

Problem: I know how to build the location and computer arrays to have the correct index, but I cannot figure out an efficient way to assign my timers in the same way… so what my code shows (Underlined) is me starting a switch statement to ID the correct facility and define the timer. This is already looking to take a LOT of code just for this one part, not to mention the logging which I hope to do in a Date(Folder)\Server(File) format and start a new folder daily, so then I can clean old logs older than 30 days by using the folders.

I know I am probably recreating the wheel here, there are lots of examples of testing servers, but I couldn’t find any with logging, notifications etc. and that is what is I am having the most trouble with.

Thanks for any help and guidance you can provide, and it is great to be a new member of the site here and learning this wonderful tool!

###########################################################
##
## Script Purpose: Check Summit Downtime Computers
## By: Bill   ----    Date: 7/9/2016
## 
## Instructions: Running this script from any PC will
##               Begin monitoring the Summit Downtime
##               Computers defined in the Serverlist.txt
##               file.
##
## Script Directory: C:\Scripts\SummitCheck
## Log Directory: C:\Scripts\SummitCheck\Logs
##
$ServerList="C:\Scripts\SummitCheck\ServerList.txt"
$Logpath="C:\Scripts\SummitCheck\Logs"
$Rawlist=Get-Content -Path $Serverlist
$Computers = @()
$Facilities = @()
Foreach ($strObj in $Rawlist)
    {
    $Items=$strObj.Split(",")
    $Computers+=$Items[0]
    $Facilities+=$Items[1]
    #$FacTimer+=[Diagnostics.Stopwatch]::StartNew()
    #$FacTimer.Reset()
    }
Function Check-PC
    {
    Param ([String]$ComputerName, [String]$Loc)
    If(Test-Connection -ComputerName $ComputerName)
        {
        #Do code for good PC
        }
    Else
        {
        #Do Code for down PC
        }
    }
Function Log-Bad
    {
    Param ([String]$ComputerName)
    $Log=$Logpath + "\$ComputerName"
    $BadTime=Get-Date
    Add-Content -path $Log -Value "$BadTime -- Connection Failed to $ComputerName!"
    }
Function Check-Timer ##This is the function I am having problems with :( and looking for a more efficient way.
    {
    Param ([String]$Loc)
    Switch($Loc)
        {
            "FWCH"
                {
                If($tmrFWCH)
                    {
                    If($tmrFWCH.isRunning)
                        {Return $tmrFWCH.Elapsed.TotalSeconds}
                    Else
                        {$tmrFWCH.Start();Return $tmrFWCH.Elapsed.TotalSeconds}
                    }
                }



        }

    }

Well, I just commented the code where I was having a problem… I was afraid underlining in the code block would mess it up.

I think I may of figured out a way to do what I need, but I am not sure if this is best practice or if it might present problems later in referencing the timers.

Foreach ($strObj in $Rawlist)
    {
    $tmrIndex=[Array]::indexof($strObj, $Rawlist)
    $Items=$strObj.Split(",")
    $Computers+=$Items[0]
    $Facilities+=$Items[1]
    $FacTimer[$tmrIndex]=[Diagnostics.Stopwatch]::StartNew()
    $FacTimer[$tmrIndex].Reset()
    }

Is it going to be inefficient to have 8 timers running at one time continuously? This script will be a constantly running script.

OK, That didn’t work :(…

Unable to index into an object of type System.Diagnostics.Stopwatch.

Persistence pays off!!!

More research into multidimensional arrays led me to a post where a Hash was defined. I had never heard of a Hash so I learned something new with this.

My Code is now working correctly, but I still do not know if having 8 timers constantly instantiated is a bad idea performance wise. I run multiple scripts on the server I plan to run this on, and I don’t want to hinder performance to awful bad.

Below is the good code, note the hash definition $FacTimer = @{} with {} instead of (). This opens up SO much more :).

$ServerList="C:\Scripts\SummitCheck\ServerList.txt"
$Logpath="C:\Scripts\SummitCheck\Logs"
$Rawlist=Get-Content -Path $Serverlist
$Computers = @()
$Facilities = @()
$FacTimer = @{}
Foreach ($strObj in $Rawlist)
    {
    $tmrIndex=[Array]::indexof($strObj, $Rawlist)
    $Items=$strObj.Split(",")
    $Computers+=$Items[0]
    $Facilities+=$Items[1]
    $FacTimer[$tmrIndex]=[Diagnostics.Stopwatch]::StartNew()
    #$FacTimer[$tmrIndex].Reset()
    Sleep -Seconds 1
    $FacTimer[$tmrIndex].Elapsed.TotalSeconds
    }

Thanks for listening to me question my way through this problem, and I would still love to hear any suggestions on a better way to do this if anyone has any more efficient ways to code this.

Personally I would have the check record the failed test-connection in a data file (For a large number of systems) or variable (for a small number of systems) with the computer name and the date and time. Subsequent checks would check to see if there was a previous failure for that computer name and then check time to see if the notification should be sent. This approach would mean that when the test-connection succeeds you would also need to check for previous failures and clear the record.

The timers by themselves should not be a big performance hit. That being said the only way to know the impact would be to measure it. For what you are describing collecting permon data for memory and cpu load as baseline for a few days and then do the same once your script is running would let you identify the impact your script has on the server.

Thanks Jonathan! I wish I had read this before I completed my script haha!

I am done however, and like I said… with my lack of experience I took a lot of lines of code, to do something most on here can probably do in under 10-20 lines. It does work however, and I learned a lot doing it.

Removed the e-mail variable values for privacy, and I changed to one log file per day instead of a log file for each PC each day. I will add the log cleaner later, it is easy.

###########################################################
##
## Script Purpose: Check Summit Downtime Computers
## By: Bill   ----    Date: 7/9/2016
## 
## Instructions: Running this script from any PC will
##               Begin monitoring.
##
## Script Directory: C:\Scripts\SummitCheck
## Log Directory: C:\Scripts\SummitCheck\Logs
##
$TimeLimit=120 #This is the time in seconds before the Email Notification is sent. 120 = 2 minutes.
$Delay=30 #Time between ping checks
$Mailfrom = 
$MailTo = 
$MailSubject = 
$SMTPServer =
$ServerList="C:\Scripts\SummitCheck\ServerList.txt"
$Logpath="C:\Scripts\SummitCheck\Logs"
if(!(Test-Path -Path $Logpath )){New-Item -ItemType directory -Path $Logpath}
$Rawlist=Get-Content -Path $Serverlist
$A=0
$Computers = @()
$Facilities = @()
$FacTimer = @{}
$Notified=@()
Foreach ($strObj in $Rawlist)
    {
    $tmrIndex=[Array]::indexof($Rawlist, $strObj)
    $Items=$strObj.Split(",")
    $Computers+=$Items[1]
    $Facilities+=$Items[0]
    $FacTimer[$tmrIndex]=[Diagnostics.Stopwatch]::StartNew()
    $FacTimer[$tmrIndex].Reset()
    $Notified+=0
    }
Function Log-Bad
    {
    Param ([String]$ComputerName,[String]$Loc,[Double]$TimerSeconds)
    $Today=(get-date -Format d).Replace("/","-")
    $Log="$Logpath\$Today.txt"
    $BadTime=Get-Date
    [Int]$Seconds=$TimerSeconds
    Add-Content -path $Log -Value "$BadTime -- $Loc -- Connection Failed to $ComputerName for $TimerSeconds Seconds!"
    }
Function Log-Good
    {
    Param ([String]$ComputerName,[String]$Loc)
    $Today=(get-date -Format d).Replace("/","-")
    $Log="$Logpath\$Today.txt"
    $GoodTime=Get-Date
    Add-Content -path $Log -Value "$GoodTime -- $Loc -- Connection Successful to $ComputerName!"
    }

Do
{
ForEach($PC in $Computers)
    {
    $MIndex=[Array]::indexof($Computers, $PC)
    If(Test-Connection $PC -Count 1 -Quiet)
        {
        Log-Good -ComputerName $PC -Loc $Facilities[$MIndex]
        $FacTimer[$MIndex].Reset()
        $Notified[$MIndex]=0
        $Facility=$Facilities[$MIndex]
        }
    Else
        {
        If($FacTimer[$MIndex])
            {
            If($FacTimer[$MIndex].IsRunning)
                {
                $Elapsed=$FacTimer[$MIndex].Elapsed.TotalSeconds
                Log-Bad -ComputerName $PC -Loc $Facilities[$MIndex] -TimerSeconds $Elapsed
                If($Elapsed -gt $Timelimit)
                    {
                    If($Notified[$MIndex] -ne 1)
                        {
                        $Loc=$Facilities[$MIndex]
                        $MailBody="The Summit Downtime Computer at $Loc has not responded in $Elapsed Seconds!The Computer Name for that Location is: $PC"
                        Try
                            {
                            Send-MailMessage -to $MailTo -From $Mailfrom -Subject $MailSubject -BodyasHTML -body $MailBody -SmtpServer $SMTPServer -ErrorAction Stop
                            }
                        Catch
                            {
                            Sleep -Seconds 5
                            Send-MailMessage -to $MailTo -From $Mailfrom -Subject $MailSubject -BodyasHTML -body "$MailBody 2nd Attempt" -SmtpServer $SMTPServer -ErrorAction SilentlyContinue
                            }
                        $Notified[$MIndex]=1
                        }
                    Else
                        {
                        If($Elapsed -gt 3600){$Notified[$MIndex]=0}
                        }
                    }
                }
            Else
                {
                $FacTimer[$MIndex].Start()
                }
            }
        Else
            {
            Write-Host -ForegroundColor Yellow "Timer not initialized!"
            Exit
            }
        }
    }

    Sleep $Delay
}
Until($A -ne 0)