I have worked on a powershell script to check if certain important services are running on the server and email me the results. When I test the script and run it in powershell, the emails come through correctly. However when I set up the task on the server and have it run the script, the emails that arrive are now all missing the service names.
Email when the script is run in powershell:
At 12-11-20 11:06, the [service] is ALL GOOD on [server].
Email when the script is run through tasks:
At 12-11-20 11:00, the is on [server].
(services and server names redacted)
Any ideas on what syntax I'm missing that it works in the testing, but not in the actual execution?
My script code (with some details redacted):
$DateTime = Get-Date -Format "MM-dd-yy hh:mm"
#Check if service is running
$servName = "[service I need to check]"
$serv = Get-Service | Where-Object { $_.DisplayName -eq $servName}
$Subject =$Serv.Name + " check on [server]"
If ($serv.Status -ne "Running") {
$Body = "At $DateTime, the " + $serv.DisplayName + " service is " + $serv.Status + " on [server]."
}
Else { $Body = "At $DateTime, the " + $serv.DisplayName + " is ALL GOOD on [server]." }
[location]\EmailAlert.ps1 -Recipient me#work.com -Subject $Subject -Body $Body
$servName = "[another service to check]"
$serv = Get-Service | Where-Object { $_.DisplayName -eq $servName}
$Subject =$Serv.Name + " check on [server]"
If ($serv.Status -ne "Running") {
$Body = "At $DateTime, the " + $serv.DisplayName + " service is " + $serv.Status + " on [server]."
}
Else { $Body = "At $DateTime, the " + $serv.DisplayName + " is ALL GOOD on [server]." }
[location]\EmailAlert.ps1 -Recipient me#work.com -Subject $Subject -Body $Body
Related
I need to go over a list of web services and query them individually and pull the details and store it into the database, I designed the code in the following way
Script 1 connects to the SNOW and pulls in the list of web servcies that i need to connect and loops them through the list and triggers a "Start-ThreadJob" with the service name as an argument
Scipt 2 connects to that specific web service (received as an argument) and connects to it and pull in the data and reports it to the database.
When i run the script 1 manually The script 1 is successfully calling all the required web services and the data is being reported to the database, however when i try to call the script 1 using the windows schdule task, The script 1 executes but the script 2 is not being triggered.
do i need to set
Any specific excution policies
Scope
Any inputs is appreciated, Here is an example for how the script 1 and 2 looks
Script 1
#Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted
##############################################################################################################
#Function Name: ErrorHandler
#Inputs: Error Type(Info, Warning or Error), Error String
#Output: None
#Description: If the error type is 'Error' then captures the last known exceptions reported and appends to the file.
# If the error type is something else then the error message is appended to the log file.
##############################################################################################################
Function ErrorHandler ($errType, $ErrorString)
{
$timestamp = Get-Date -Format o
if ($errType -eq 'Error')
{
$errorstr = $timestamp + ' : ' + $errType + ' - ' + $ErrorString + $Error[0].Exception
}
else
{
$errorstr = $timestamp + ' : ' + $errType + ' - ' + $ErrorString
}
$errcounter = 0
$maxerrcounter = 5
do
{
try
{
$errorstr | Out-File $ErrFile -Append
break
}
catch
{
Start-Sleep 10
$errcounter++
}
}while ($errcounter -le $maxerrcounter)
}
##############################################################################################################
#Initialize Variable
##############################################################################################################
#Key file path
$CredFilePath = "E:\WIS\MonitorConfiguration\Schduled\Stage\creds\"
#Error file name
$ErrFile = 'C:\MonitorReportLogs\Sitescope_Config_Pull.log'
$authorization = $null
#SNOW API server fqdn
$SnowFQDNhost = "<Server Name>"
###################################
#Build Snow API authorization header
###################################
$user= Get-Content $CredFilePath'SNOW_User.txt'
$KeyFile = $CredFilePath+'AES.key'
$key = Get-Content $KeyFile
$EncryptedPW = Get-Content $CredFilePath'SNOW_Pass.txt' | ConvertTo-SecureString -Key $key
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($EncryptedPW)
$pass = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$pair = "$($user):$($pass)"
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
$authorization = "Basic $encodedCreds"
#SNOW API authorization Header assignment
$Headers = #{
Authorization = $authorization
}
###################################
#Query SNOW for the list of sitescope instances
###################################
$snowsysurl="https://" + $SnowFQDNhost + "/api/now/table/u_cmdb_relationship_report?sysparm_query=u_service.name=BSM SiteScope - Production&sysparm_fields=u_hardware.fqdn"
try
{
$info=Invoke-WebRequest -Uri $snowsysurl -Headers $Headers
}
catch
{
$errStr = "SNOW Sitescope server list API request for the URL: "+ $snowsysurl +" failed - "
ErrorHandler 'Error' $errStr
}
###################################
#Process each sitescope instance monitor data
###################################
If($info -eq $null){
$errStr = "Unhandled Exception parsing data for " + $SnowFQDNhost + " exiting Script."
ErrorHandler 'Error' $errStr
Exit
}
else
{
#convert the JSON response to PS Custom data object dictionary
$snowContent = $info | ConvertFrom-Json
#Query each sitescope on the list and process the configuration data
$jobCounter = 0
if (($snowContent.result.'u_hardware.fqdn' -ne $null) -and ($snowContent.result.'u_hardware.fqdn' -ne ''))
{
foreach($obj in $snowContent.result.'u_hardware.fqdn')
{
if (($obj -ne $null) -and ($obj -ne ''))
{
Start-ThreadJob -ScriptBlock {param($hostname) E:\WIS\MonitorConfiguration\Schduled\Stage\SIS_Config_DB_Push.ps1 $hostname}`
-ArgumentList ($obj) -ThrottleLimit 10 -Name $obj
$errStr = "Triggered the script for server : "+ $obj
ErrorHandler 'Info' $errStr
}
}
}
else
{
$errStr = "SNOW Sitescope server list API request for the URL: "+ $snowsysurl +" returned 0 records."
ErrorHandler 'Error' $errStr
}
}
###################################
#Garbage Collection
###################################
$info = $null
$snowContent = $null
Script 2: (Limited Version)
#Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted
##############################################################################################################
#Function Name: ErrorHandler
#Inputs: Error Type(Info, Warning or Error), Error String
#Output: None
#Description: If the error type is 'Error' then captures the last known exceptions reported and appends to the file.
# If the error type is something else then the error message is appended to the log file.
##############################################################################################################
Function ErrorHandler ($errType, $ErrorString)
{
$timestamp = Get-Date -Format o
if ($errType -eq 'Error')
{
$errorstr = $timestamp + ' : ' + $errType + ' - Server Name: ' + $SiSServerName + ' - ' + $ErrorString + $Error[0].Exception
}
else
{
$errorstr = $timestamp + ' : ' + $errType + ' - Server Name: ' + $SiSServerName + ' - ' + $ErrorString
}
$errcounter = 0
$maxerrcounter = 5
do
{
try
{
$errorstr | Out-File $ErrFile -Append
break
}
catch
{
Start-Sleep 10
$errcounter++
}
}while ($errcounter -le $maxerrcounter)
}
$obj = $args[0]
#Error file name
$ErrFile = 'C:\MonitorReportLogs\Sitescope_Config_Pull.log'
$errStr = "Start processing data for " + $obj
ErrorHandler 'Info' $errStr
I have a script that sends an email when a file exceeds a maximum set size.
All of my files come from a XML-file that concludes the path and max size.
The foreachthat I do to check every size (and to see if it exceeded the max size) looks like:
foreach ($item in $list)
{
if($item.Size -gt $item.TriggerSize -And (Test-Path $item.Path))
{
$body =
"The file " + $item.Name + " is reaching his max size!
<br /><br /> Current size: "+$item.Size.ToString(".00") + " " + $byteSize.Substring(1) +
"<br /> Maximum size: " + $item.MaxSize + " " + $byteSize.Substring(1)
Send-MailMessage -port 587 -From $from -BodyAsHtml -Encoding $enc -To $to -Subject $subject -Body $body -UseSsl -Credential $credentials
}
}
The code is working perfectly, I get an email that looks like:
The file editix2017.exe is reaching his max size!
Current size:84.93 Mb
Maximum size:100 Mb
The problem I have however is that when I work with more than 1 file, it sends an email for every file that exceeded its max limit.
This means that if I have 2 files that exceeded their max size, it sends 2 seperate emails.
How could I encapsulate every one of the exceeded files in a list and send just one email after?
Use $body += to add text to $body string each time, then move Send-MailMessage outside the loop so it only sends an email after each file has been evaluated:
foreach ($item in $list)
{
if($item.Size -gt $item.TriggerSize -And (Test-Path $item.Path))
{
$body += "The file $($item.Name) is reaching his max size!<br />
Current size: $($item.Size.ToString(".00")) $($byteSize.Substring(1))<br />
Maximum size: $($item.MaxSize) $($byteSize.Substring(1))<br /><br />"
}
}
Send-MailMessage -port 587 -From $from -BodyAsHtml -Encoding $enc -To $to -Subject $subject -Body $body -UseSsl -Credential $credentials
edit: I've updated the way $body is constructed to use subexpressions $() as these return only the properties of the object making your string construction much simpler.
This might do it (untested). It defines $body as an empty string, which we then increment in the loop with the messages about the files and then I have moved the send-mailmessage outside of the loop so that we do it at the end, but only if $body has contents:
$body = ""
foreach ($item in $list)
{
if($item.Size -gt $item.TriggerSize -And (Test-Path $item.Path))
{
$body +=
"The file " + $item.Name + " is reaching his max size!
<br /><br /> Current size: "+$item.Size.ToString(".00") + " " + $byteSize.Substring(1) +
"<br /> Maximum size: " + $item.MaxSize + " " + $byteSize.Substring(1) + "<br /><br />"
}
}
If ($body) { Send-MailMessage -port 587 -From $from -BodyAsHtml -Encoding $enc -To $to -Subject $subject -Body $body -UseSsl -Credential $credentials }
I have a PowerShell script that generates emails from a list of contacts imported from a CSV file:
# Get Credential
$Credential = & "C:\Powershell Scripts\Windows\Get-CredentialFromWindowsCredentialManager.ps1" ms.outlook.15:my.email#domain.com
# Get Contacts
$contacts = Import-Csv -Path "C:\Powershell Scripts\Email\Contacts.csv"
# Compose Email for each contact
foreach( $contact in $contacts )
{
Write-Output "Creating email for: $($contact.FirstName) $($contact.LastName)"
$To = "$($contact.FirstName) $($contact.LastName) <$($contact.Email)>"
$From = "My Email <my.email#domain.com>"
$Subject = "$($contact.FirstName), I have a suggestion for you!"
$Body = "<html></html>"
$SMTPServer = "smtp.office365.com"
$Port = 587
Send-MailMessage -To $To -From $From -Subject $Subject -SmtpServer $SMTPServer -Credential $Credential -UseSsl -Body $Body -BodyAsHtml -Port $Port
# Due to the Message Send rate limit (30 per minute) I added this to slow the rate down
Start-Sleep -Seconds 10
}
Every 10 minutes I get the following SMTP Exception:
Send-MailMessage : Service not available, closing transmission channel. The
server response was: 4.4.1 Connection timed out. Total session duration:
00:10:08.3716645
At C:\Powershell Scripts\Email\SendEmail.ps1:17 char:2
+ Send-MailMessage -To $To -From $From -Subject $Subject -SmtpServer $SMTPServer ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpException
+ FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage
Are there any settings that I can modify or changes in code that will prevent this?
Don't take this personally, this is just a general rant, but you've got a script that performs an action against a resource that is out of your control. As such, you can't just expect an SMTP connection to succeed and wonder what you should do to prevent it from failing. Deep breath. The answer is to consider edge cases and actually cater for them in your code. Granted, you've got a sleep in there to try to not fall foul of rate limiting, but that's not a robust solution. In this instance, a simple exception handler around the Send-MailMessage call would suffice. You could include a number of retries and small sleep delays.
A "maximum number of acceptable failures" threshold could be used to break out of the retry loop and cause some kind of inward alerting, Etc.
Long story short, don't just throw spaghetti at the wall with your eyes closed.
</rant>
An example, but not necessarily the tidiest solution:
[Int32] $maxAttempts = 5;
[Int32] $failureDelay = 2;
[Int32] $interMessageDelay = 10;
[Int32] $numAttempts = 0;
[Boolean] $messageSent = $false;
:CONTACT foreach ( $contact in $contacts ) {
$numAttempts = 1;
$messageSent = $false;
while ( ($numAttempts -le $maxAttempts) -and (! $messageSent) ) {
try {
Write-Host -Object ( 'Sending message, attempt #{0} of #{1}...' -f $numAttempts, $maxAttempts );
Send-MailMessage <blah>;
Write-Host -Object "Ok.`n";
$messageSent = $true;
} #try
catch [System.Exception] {
# ^^^^^ This exception type needs changing, but I don't know the full
# type of your failure.
Write-Host -Object 'Failed.';
if ( $numAttempts -ge $maxAttempts ) {
Write-Host -Object "ERROR : Maximum attempts reached - aborting.`n";
continue CONTACT;
} else {
Write-Host -Object ( 'Sleeping for {0} second(s)...' -f $failureDelay );
Start-Sleep -Seconds $failureDelay;
$numAttempts++;
} #else-if
} #catch
} #while
Write-Host -Object ( 'Sleeping for {0} second(s)...' -f $interMessageDelay );
Start-Sleep -Seconds $interMessageDelay;
} #foreach
I am writing this powershell script for automating TFS deployment. Here is the section of the script that is throwing the error Missing statement block in switch statement clause. The syntax looks correct to me but I can't seem to resolve the error. Any ideas?
function core ([string]$EnVar)
{
# Set the build parameters
$params="environment="+$EnVar+";SQLServer="+$SQLServer+";IISServer="+$IISServer+";DBName="+$DBName
write-output $params >> $OutputFile
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe $DeploymentScript /p:$params >> $OutputFile 2>&1
write-output $params >> $OutputFile
write-output $LastExitCode >> $OutputFile
if ($LastExitCode -ne 0)
{
write-output "An error has occured." >> $OutputFile
$outline = "Updating build quality to Failed " + $Environment + " Deployment."
write-output $outline >> $OutputFile
switch ($EnVar)
{
"Test" {$build.Quality = "Failed Test Deployment"}
"Stage" {$build.Quality = "Failed Stage Deployment"}
"Prod" {$build.Quality = "Failed Production Deployment"}
default {$build.Quality = "Rejected"}
}
$build.Save()
$cmdLine = "/ID 2 /L ""Operation Logs"" /T ERROR /SO $tfsProject.DeployTo$EnVar /D ""($buildNumber) deployed by ($requestedby) FAILED! Deployment log file: $OutputFile """
invoke-expression "$tool $cmdline"
write-output "Sending failure email." >> $OutputFile
$to = $emailRequestedBy
$body = "<html>Deployment log file: """+ $OutputFile + """</html>"
$subject = $tfsProject + " " + $EnVar + " Deployment failed"
send-SMTPmail -to $to -from "tfsservice#vistex.com" -subject $subject -html -body $body
exit(1)
}
else
{
$outline = "Successfully deployed to " + $EnVar + "."
write-output $outline >> $OutputFile
$outline = "Updating build quality to Deployed to " + $EnVar + "."
write-output $outline >> $OutputFile
switch ($EnVar)
{
"Test" ($build.Quality = "Deployed to Test"}
"Stage" {$build.Quality = "Deployed to Stage"}
"Prod" {$build.Quality = "Deployed to Production"}
default {$build.Quality = "Rejected"}
}
$build.Save()
$cmdLine = "/ID 1 /L ""Operation Logs"" /T SUCCESS /SO $tfsProject.DeployTo$EnVar /D ""($buildNumber) deployed by $requestedby successfully finish. Deployment log file: $OutputFile """
invoke-expression "$tool $cmdline"
write-output "Sending success email." >> $OutputFile
$to = $emailRequestedBy
$body = "<html>Deployment log file: """+ $OutputFile + """</html>"
$subject = $tfsProject + " " + $EnVar + " Deployment successfully completed"
send-SMTPmail -to $to -from "tfsservice#vistex.com" -subject $subject -html -body $body
}
}
Found the issue. It's a typo... You have a ( instead of a { in the "Test" case:
else
{
$outline = "Successfully deployed to " + $EnVar + "."
write-output $outline >> $OutputFile
$outline = "Updating build quality to Deployed to " + $EnVar + "."
write-output $outline >> $OutputFile
switch ($EnVar)
{
"Test" ($build.Quality = "Deployed to Test"}
"Stage" {$build.Quality = "Deployed to Stage"}
"Prod" {$build.Quality = "Deployed to Production"}
default {$build.Quality = "Rejected"}
}
Update the line to be:
"Test" {$build.Quality = "Deployed to Test"}
We tend to drag and drop messages from outlook into Windows Explorer so I need to rename the default message filename so that the files are searchable/readable from Explorer.
I have managed to put together the following code that almost renames an outlook file on a network folder from the default "Subject.msg" to "To - Subject - ReceivedDate - hhmmss.msg". The only problem is that the rename step does not work as I believe the Outlook process is locking the file. I would appreciate help to avoid the locking and rename the files? Also, I am not sure what happens if there are multiple people in the To list, I would be happy to take the first name in the To list? Here is my effort:
$olMailItemPath = "W:\Marketing\Prospects\Emails\*"
Write-Host $olMailItemPath
$SourceFiles = Get-Item -path $olMailItemPath -include *.msg
$outlook = New-Object -comobject outlook.application
$namespace = $outlook.GetNamespace("MAPI")
function cleanName($aname)
{
$aname = $aname -replace "'"
$aname = $aname -replace ":"
$aname = $aname -replace "#"
$aname = $aname -replace "-"
return ($aname.trim())
}
function cleanSubject($subject)
{
$subject = $subject -replace 'Re:'
$subject = $subject
return (' - ' + $subject.trim() + ' - ')
}
foreach ($msg in $SourceFiles){
$olMailItem = $NameSpace.OpenSharedItem($msg)
$EmailTo = $olMailItem.To
$EmailSubject = $olMailItem.Subject
$DateRecieved = $olMailItem.ReceivedTime
$newfilename = (cleanName($EmailTo)) + (cleanSubject($EmailSubject)) + $DateRecieved.ToString("yyyyMMdd - hhmmss") + ".msg"
# Write-Host "Email Sent To: $EmailTo "
# Write-Host "Subject: $EmailSubject "
# Write-Host "Date Recieved: $DateRecieved"
Write-Host $msg
Write-Host $newfilename
Rename-Item $msg $newfilename
}
p.s. [Inserted # 18 Jun 2013] In answer to Athom, I know Outlook is locking the file as I get the following error:
Rename-Item : The process cannot access the file because it is being used by another process.
At C:\Users\Bertie\Dropbox\Programming\Powershell\Rename Outlook Messages.ps1:41 char:16
+ Rename-Item <<<< -path $msg -newname $newFileName
+ CategoryInfo : WriteError: W:\Marketing\Prospects\Emails\new.msg:String) [Rename-Item], IOException
+ FullyQualifiedErrorId : RenameItemIOError,Microsoft.PowerShell.Commands.RenameItemCommand
However, when I close outlook (which is initiated by the powershell script), I can then run the Rename-Item command and it run's successfully.
How's this?
Essentially the changes I have mades are:
Your renaming loop now throws its output to a hashtable.
Stop-Process kills Outlook.
Another loop then does the renaming.
# Declare the hastable to store the names, then generate the names.
$nameHash = #{}; Foreach ($msg in $SourceFiles){
# Do the Outlook thing
$olMailItem = $NameSpace.OpenSharedItem($msg)
$EmailTo = $olMailItem.To
$EmailSubject = $olMailItem.Subject
$DateRecieved = $olMailItem.ReceivedTime
$newfilename = (cleanName($EmailTo)) + (cleanSubject($EmailSubject)) + $DateRecieved.ToString("yyyyMMdd - hhmmss") + ".msg"
# Write-Host "Email Sent To: $EmailTo "
# Write-Host "Subject: $EmailSubject "
# Write-Host "Date Recieved: $DateRecieved"
# Store the names
$nameHash.Add("$msg","$newfilename")
}
# Kill Outlook.. Then wait....
Stop-Process -Name Outlook -Force
Start-Sleep -m 500 # You might be able to remove this - depends how beefy your CPU is.
# Rename
ForEach ($item in $nameHash.GetEnumerator()) {
# Testing >>-->
echo $item.Name
echo $item.Value
# <--<< Testing
Rename-Item $item.Name $item.Value
}