Powershell - Check IP, loop to retry and send email - powershell

I've been trying to script so powershell checks our external ip - if its unchanged - do nothing just log its been checked.
If the script thinks its changed, retry, and if after 5 attempts it has changed then send an email.
I've included where I got to, but I can't (tiredness or stupidity) get this to work today so throwing this out to the hive mind for help!
attached is where ive got too! Cheers CM
#find old ip
$oldip = gc .\ip.txt
#get current ip address
$currentip = (New-Object net.webclient).downloadstring("http://api.ipify.org")
#current date and time
$currentTime = Get-Date -format "dd-MMM-yyyy HH:mm:ss"
#script location
$scriptpath = $MyInvocation.MyCommand.Definition
[string]$dir = Split-Path $scriptpath
set-location $dir
#set the smtp server URL here
$smtpServer = "smtp.my.com"
#Set the Sender Name or Email here
$sender = "sender#my.comm"
#Enter the email reciepients here - you can send to multiple using format: "user1#gmail.com", "user2#hotmail.com";
$users = "email#my.com";
#Enter the subject title the reciepient see's here
$subject = "IP Has Changed"
#Type out the email contents here
$body = "Dear Team Member,
Your IP address at has just changed from: $oldip
And is now set too: $currentip
Regards."
#*****************************************************************************************************************************************
do {
$Result = ($oldip -ne $currentip)
if ($Result.Status -eq "Success") {
Write-Output "IP Address Unchanged - Monitoring Continues."
Add-Content $PSScriptRoot\IP_Check_Log.txt "$currentTime - IP Unchanged. "
Start-Sleep -s 2
Exit 0
}
else {
Write-Output "Ping Failed!"
Add-Content $PSScriptRoot\IP_Check_Log.txt "$currentTime - IP Check Failed - Retrying"
$Failures += 5
}
} until ($Failures -ge $FailureThreshold)
Write-Output "IP Address Changed!"
Add-Content $PSScriptRoot\IP_Check_Log.txt "$currentTime - IP Adderss Changed. "
Add-Content $PSScriptRoot\IP_Check_Log.txt "*****************************************************************************"
#be sure to alter the SMTP server port here if needed.
$smtp = New-Object Net.Mail.SmtpClient($smtpServer, 587)
$smtp.EnableSsl = $true
#Enter the sending email address and password here
$smtp.Credentials = New-Object System.Net.NetworkCredential("email#my.com", "password");
$smtp.Send($sender, $user, $subject, $body)

Perhaps easier than a do{..} until(..) here is to loop using a for loop and I would use a try{..} catch{..} on this.
Inside the loop get the ip address fresh, don't get that once and keep looping over that instance.
Something like this perhaps (left out the emailing stuff)
$logFile = Join-Path -Path $PSScriptRoot -ChildPath 'IP_Check_Log.txt'
$FailureThreshold = 5
# find old ip
$oldip = [IPAddress](Get-Content .\ip.txt)
$ipchanged = $false
$currentip = $null
for ($i = 0; $i -lt $FailureThreshold; $i++) {
try {
# try to get the get current ip address
$currentip = [IPAddress](New-Object net.webclient).DownloadString("http://api.ipify.org")
if ($currentip -eq $oldip) {
Write-Output "IP Address Unchanged - Monitoring Continues."
Add-Content -Path $logFile -Value "$currentTime - IP Unchanged. "
exit 0
}
else {
$ipchanged = $true
}
}
catch {
$currentip = $null
$ipchanged = $false
Write-Output "Ping Failed!"
Add-Content -Path $logFile -Value "$currentTime - IP Check Failed - Retrying"
}
Start-Sleep -Seconds 2
}
# test if the above returned a $currentip not $null and if boolean $ipchanged is $true
if (($currentip) -and $ipchanged) {
Write-Output "IP Address Changed!"
Add-Content -Path $logFile -Value "$currentTime - IP Address Changed. "
Add-Content -Path $logFile -Value "*****************************************************************************"
# refresh the .\ip.txt file to store the new ip?
Set-Content -Path .\ip.txt -Value $currentip.IPAddressToString
# send the email
}
I use casts to [IPAddress] because if the .DownloadString() fails OR it returns something which is not an IP address, you will enter the catch block so you can deal with the error

Related

Sending email with CSV from a Running Process check

I'm trying to check if a process is running on a remote computer (Eventually will be about 100 computers). If the process is not running, I'd like it to put the computername/IP into a CSV and then email that out. If the process is running on all machines, I'd like the script to not send an email out at all. To do this, I'd like to test the machines first to check they're online (If they're offline, we've either got bigger problems or it's off for a reason, but that's not what this process is checking for.
I'm going to be testing this script on a few machines with just the notepad process at the moment as it's something I can do on a test machines reletively quickly.
I'm a little stuck at the moment, because I don't know how to get the results from the process check to be put into a CSV and then emailed. In the code snippet below, it's not generating the outfile, but have left the variable I was testing with and the path to where the attachment would be in the send-mailmessage. Any advice will be appreciated, I'm still learning powershell at the moment so don't know all the tricks and tips yet.
Cheers
# Mail server Configuration
$MailServer = "mail.server.co.uk"
$MailFrom = MailFrom#server.co.uk"
# Mail Content Configuration
$MailTo = "Recipient#Server.co.uk"
$MailSubjectFail = "INS Process not running on $DAU"
$MailBodyFail = "The INS Process on the DAU $DAU is not running. Please manually start process on DAU $DAU"
# Process Info
$Process = "Notepad"
$ProcessIsRunning = { Get-Process $Process -ErrorAction SilentlyContinue }
#Results Info
$Exportto = "C:\Scripts\Content\INSChecker\Results.csv"
# Get DAU Information
foreach($line in (Get-Content C:\Scripts\Content\INSChecker\INSList.cfg)){
$line = $line.split(",")
$DAU = $line[0]
$DAUIP = $line[1]
# Test Connection to INS DAU
write-host "Testing: $DAU / $DAUIP"
$TestDAU = Test-Connection $DAU -quiet
$TestDAUIP = Test-Connection $DAUIP -quiet
write-host "Tests: $TestDAU / $TestDAUIP"
If($TestDAU -ne 'True'){
If($TestDAUIP -ne 'True'){
write-host "DNS Not resolved for $DAU"
Write-Output "INS $DAU/$DAUIP is OFFLINE" | Out-File C:\Scripts\Content\INSChecker\INSProcessCheck.log -append
}
}
Else{
# Get Process Running State and Send Email
if(!$ProcessIsRunning.Invoke()) {
Send-MailMessage -To $MailTo -From $MailFrom -SmtpServer $MailServer -Subject $MailSubjectFail -Body $MailBodyFail -Attachments C:\Scripts\Content\INSChecker\Results.csv
} else {
"Running"
}
}
}
Hopefully this gives a you a hint on where to begin and how to approach the problem, I have removed the irrelevant parts of the script and only left the logic I would personally follow.
The result of $report should be an object[] (object array) which should be very easy to manipulate and very easy to export to CSV:
#($report).where({ $_.SendMail }) | Export-Csv $exportTo -NoTypeInformation
I'll leave you the remaining tasks (attach the CSV, send the emails, etc) for your own research and design.
$ErrorActionPreference = 'Stop'
# Process Info
$Process = "Notepad"
$ProcessIsRunning = {
param($computer, $process)
# On Windows PowerShell -ComputerName is an option,
# this was removed on PS Core
try
{
$null = Get-Process $process -ComputerName $computer
# If process is running return 'Running'
'Running'
}
catch
{
# else return 'Not Running'
'Not Running'
# send a Warning to the console to understand why did this
# fail ( couldn't connect or the process is not running? )
Write-Warning $_.Exception.Message
}
}
#Results Info
$ExportTo = "C:\Scripts\Content\INSChecker\Results.csv"
$exportProps = 'Server', 'IP', 'Ping', 'DNSResolution', 'Process', 'SendMail'
# Get DAU Information
$report = foreach($line in Get-Content path/to/file.txt)
{
$status = [ordered]#{} | Select-Object $exportProps
$DAU, $DAUIP = $line = $line.split(",")
$status.SendMail = $false
$status.Server = $DAU
$status.IP = $DAUIP
# Test ICMP Echo Request and DNS Resolution
$ping = Test-Connection $DAUIP -Quiet
$dns = Test-Connection $DAU -Quiet
$status.Ping = ('Failed', 'Success')[$ping]
$status.DNSResolution = ('Failed', 'Success')[$dns]
$status.Process = & $ProcessIsRunning -computer $DAUIP -process $Process
if(-not $ping -or -not $dns -or $status.Process -eq 'Not Running')
{
$status.SendMail = $true
}
[pscustomobject]$status
}
#($report).where({ $_.SendMail }) # => This is what should be mailed

How to ping continuously in the background in powershell?

This is my first program in powershell, Im trying to get from the user input and then pinging the IP address or the hostname, Creating text file on the desktop.
But if the user wants the add more than one IP I get into infinite loop.
Here Im asking for IP address.
$dirPath = "C:\Users\$env:UserName\Desktop"
function getUserInput()
{
$ipsArray = #()
$response = 'y'
while($response -ne 'n')
{
$choice = Read-Host '
======================================================================
======================================================================
Please enter HOSTNAME or IP Address, enter n to stop adding'
$ipsArray += $choice
$response = Read-Host 'Do you want to add more? (y\n)'
}
ForEach($ip in $ipsArray)
{
createFile($ip)
startPing($ip)
}
}
Then I creating the file for each IP address:
function createFile($ip)
{
$textPath = "$($dirPath)\$($ip).txt"
if(!(Test-Path -Path $textPath))
{
New-Item -Path $dirPath -Name "$ip.txt" -ItemType "file"
}
}
And now you can see the problem, Because I want the write with TIME format, I have problem with the ForEach loop, When I start to ping, And I cant reach the next element in the array until I stop
the cmd.exe.
function startPing($ip)
{
ping.exe $ip -t | foreach {"{0} - {1}" -f (Get-Date), $_
} >> $dirPath\$ip.txt
}
Maybe I should create other files ForEach IP address and pass params?
Here's a old script I have. You can watch a list of computers in a window.
# pinger.ps1
# example: pinger yahoo.com
# pinger c001,c002,c003
# $list = cat list.txt; pinger $list
param ($hostnames)
#$pingcmd = 'test-netconnection -port 515'
$pingcmd = 'test-connection'
$sleeptime = 1
$sawup = #{}
$sawdown = #{}
foreach ($hostname in $hostnames) {
$sawup[$hostname] = $false
$sawdown[$hostname] = $false
}
#$sawup = 0
#$sawdown = 0
while ($true) {
# if (invoke-expression "$pingcmd $($hostname)") {
foreach ($hostname in $hostnames) {
if (& $pingcmd -count 1 $hostname -ea 0) {
if (! $sawup[$hostname]) {
echo "$([console]::beep(500,300))$hostname is up $(get-date)"
$sawup[$hostname] = $true
$sawdown[$hostname] = $false
}
} else {
if (! $sawdown[$hostname]) {
echo "$([console]::beep(500,300))$hostname is down $(get-date)"
$sawdown[$hostname] = $true
$sawup[$hostname] = $false
}
}
}
sleep $sleeptime
}
pinger microsoft.com,yahoo.com
microsoft.com is down 11/08/2020 17:54:54
yahoo.com is up 11/08/2020 17:54:55
Have a look at PowerShell Jobs. Note that there are better and faster alternatives (like thread jobs, runspaces, etc), but for a beginner, this would be the easiest way. Basically, it starts a new PowerShell process.
A very simple example:
function startPing($ip) {
Start-Job -ScriptBlock {
param ($Address, $Path)
ping.exe $Address -t | foreach {"{0} - {1}" -f (Get-Date), $_ } >> $Path
} -ArgumentList $ip, $dirPath\$ip.txt
}
This simplified example does not take care of stopping the jobs. So depending on what behavior you want, you should look that up.
Also, note there there is also PowerShell's equivalent to ping, Test-Connection

Writing a powershell script to monitor a log file

I am trying to write a script that will monitor a log file for time modified. If the file has been monitored on the last minute send me an email saying it is all good. IF the log file is older then a minute and the time is between 6am and 9pm send me an email. IF between 9pm and 6am reboot the server. except it is rebooting the server no matter what
# Set perameters here
$uncPath = 'Unc path to log file'
$Server = 'ServerName'
$min = Get-Date 06:00
$max = Get-Date 21:00
$now = Get-Date -Format hh:mm:ss
# Look at the Printstream Log Files and check last modified time
$files = (Get-ChildItem -Path $uncPath -Filter '*PrintstreamBroker*' -File |
Where-Object { $_.LastWriteTime -Gt (Get-Date).AddMinutes(-1) }).FullName
#If there file is good do nothing, and send email
if ($files) {
$Subject = 'Printstream Broker Is Running'
$message = 'The Printstream Broker Is Running There Is Nothing To Do'
$emailTo = '1#1.com'
}
Else ($min.TimeOfDay -lt $now -and $max.TimeOfDay -gt $now){
{
$Subject = 'Printstream Broker Is Not Running'
$message = 'Printstream Broker Is Not Running Please Log Into $servername and start the Printstream Monarch Broker'
$emailTo = '1#1.com'
}
}
Else {
Restart-Computer –ComputerName $server -Force
$Subject = 'Monarch Server Rebooted'
$message = 'The Monarch Server Has Been Rebooted'
$emailTo = '1#1.com'
}
}
# create a email
$mailParams = #{
From = 'PrintstreamBrokerDEV#visionps.com'
To = $emailTo
Subject = $Subject
Body = $message
SmtpServer = 'smtpserver'
# any other parameters you might want to use
}
# send the email
Send-MailMessage #mailParams
You have if else else.
It should be if elseif else.

Change $smtpTo for email based on if statement

Inside my register event action I have an if statement that checks to see if the paths match, if they do I set the email to $smtpTo to the proper email address. But I get an error "The parameter 'to' cannot be an empty string. I know the paths are correct as they Write to console in the if statement.
$MonitorFolder = Get-Content "C:\Desktop\ScanFTPDeptClients\OutgoingPathlist.txt"
$MonitorStopFile = "monitor.die"
$smtpServer = "mail.test.org"
$smtpFrom = "SYSTEMFUNCTION#test.org"
$smtpSubject = "Completed files have arrived in FTP"
$smtpTo= ""
$SourceID = "MonitorFiles"
foreach ($path in $MonitorFolder){
$i+=1
$watcher = New-Object System.IO.FileSystemWatcher $path
#Files only. Default is files + directory
$watcher.NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite'
#Using a thread-safe collection (in global scope so Action-block can reach it) to store the log just to be safe.
$global:newFiles = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
$newFileSubscription = Register-ObjectEvent $watcher Created -SourceIdentifier $i+"NewFileCreated" -Action {
Write-Host "New file named '$($Event.SourceEventArgs.Name)' arrived in $(split-path $Event.SourceEventArgs.FullPath)"
#Check the path
$deptClient= "$(split-path $Event.SourceEventArgs.FullPath)"
#set the email based on folder path
if ("$($deptClient)" -eq "\\vavm\FTP\C\O\RuthWebster"){
Write-host "$($deptClient)"
$smtpTo = "test#test.org"
}
#add files to content of body email
$global:newFiles.Add("`n[$(Get-Date -Format HH:mm:ss)]`t $($Event.SourceEventArgs.Name)has been completed and arrived in $(split-path $Event.SourceEventArgs.FullPath) ")
if($Event.SourceEventArgs.Name -eq $MonitorStopFile) {
Write-Host "Monitoring stopped"
#Stop monitoring
Unregister-Event -SubscriptionId $newFileSubscription.Id
#Dispose FileSystemWatcher
$watcher.Dispose()
}
}
}
$smtp = New-Object -TypeName "Net.Mail.SmtpClient" -ArgumentList $smtpServer
while ($watcher.EnableRaisingEvents -or $global:newFiles.Count -gt 0) {
#Sleep
Start-Sleep -Seconds 10
if($global:newFiles.Count -gt 0) {
#Convert list of strings to single string (multiline)
$smtpbody = $global:newFiles
$smtp.Send($smtpFrom, $smtpTo, $smtpSubject, $smtpBody)
#Mail sent, Empty array
$global:newFiles.Clear()
}
}
Try with $smtpTo as a global variable. Replace all $smtpTo with $global:smtpTo.
Btw. you don't need to wrap split-path in a string and subexpression. Try:
$deptClient= split-path $Event.SourceEventArgs.FullPath
#set the email based on folder path
if ($deptClient -eq "\\vavm\FTP\C\O\RuthWebster"){
Write-host $deptClient
$smtpTo = "test#test.org"
}

How to get PowerShell to send email with file paths

I have the following script where I'm trying to get the path location of some files emailed to me but I am having a problem passing a variable to a function. I get the email but the body is empty.
Can someone help me out here?
Thanks,
Lamar Thomas
function sendMail($VirusLoc){
Write-Host "Sending Email"
#SMTP server name
$smtpServer = "mailhost.maxor.com"
#Creating a Mail object
$msg = new-object Net.Mail.MailMessage
#Creating SMTP server object
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
#Email structure
$msg.From = "me#xxxxxx.com"
$msg.ReplyTo = "me#xxxxx.com"
$msg.To.Add("me#xxxxx.com")
$msg.subject = "CryptoLocker Virus Found On pcname"
$msg.body = $VirusLoc
#Sending email
$smtp.Send($msg)
}
Clear-Host
$arg = ("\\mxfsa01\Supsys\Red River Wholesale-old")
Set-Location $arg
$Files = Get-ChildItem -Recurse |Where-Object {$_.name -Like "Decrypt_Instruction.txt"} | % {
# Write-Host $_.FullName
$VirusLoc = $_.FullName
Write-Host $VirusLoc
}
sendMail
You need to provide an argument for the VirusLoc parameter of your function e.g.:
sendMain -VirusLoc $VirusLoc
BTW that variable is only holding a single filename. Is there are multiple files you may want to change this line:
$VirusLoc = $_.FullName
to
$VirusLoc += "`n" + $_.FullName