Email Errors with Powershell - powershell

I have a PowerShell script that looks for a file in a folder and moves it to another folder, and renames the file with a date extension.
Like this:
$a = "\\server\Users\Desktop\why agile.docx"
$f = "\\server\Users\desktop\Archive\why agile.docx"
Move-item $a $f
Function renameFile ($location, $filename, $extension)
{
$d = get-date -uformat "%Y%m%d"
$old = $location + $filename + $extension
$new = $filename + "_" + $d + $extension
rename-item $old $new
}
renamefile -location "\\server\Users\desktop\Archive\" -filename "why agile" -extension ".docx"
My question is: How can I add to this script to email out any error messages, or if there are missing files, duplicate files or if the process fails for some reason (time out, etc...)?
Thanks,

Clear the $Error automatic variable and set $ErrorActionPreference to SilentlyContinue before you start. Send an e-mail with the content of the $Error variable if it's not empty after you finished:
$Error.Clear()
$eap = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
renamefile ...
if ($Error) {
Send-MailMessage -From $sender -To $recipient -Body ($Error -join "`n") ...
}
$ErrorActionPreference = $eap
For taking care of missing or duplicate files add appropriate checks.

To expand on the other answer, you can wrap your code in a try block and then in your catch block, email the errors.
Something like
try {
rename file ...
}
catch [Exception] {
Send-MailMessage ...
}

Here's what I ended up doing. I used the try { } catch { } and it works great. Thanks to everyone for helping...it got me pointed in the right direction. I'm an newbie with Powershell.
try
{
$a = "\\servername\Users\Desktop\agile.docx"
$b = "\\servername\Users\Desktop\Archive\agile.docx"
Move-item $a $b -ErrorAction stop
Function renameFile ($location, $filename, $extension)
{
$d = get-date -uformat "%Y%m%d"
$old = $location + $filename + $extension
$new = $filename + "_" + $d + $extension
rename-item $old $new
}
renamefile -location "\\servername\Users\desktop\Archive\" -filename "Agile" -extension ".docx"
}
Catch
{
$ErrorMessage = $_.Exception.Message
$FailedItem = $_.Exception.ItemName
Send-MailMessage -From my.name#mycompany.com -To her.name#mycompany.com -Subject "Files Failed to Transfer to Archive Folder!" -SmtpServer smtp... -Body "The error message is: '$ErrorMessage'"
Break
}

Related

List all files that were moved in an email Body - Powershell

Trying to accomplish the following:
1)Move files from multiple sources into multiple destinations.
Therefore i listed sources under a SOURCE column, and destinations under a DESTINATION column (WORKING)
2)Send an email with a list of files that were moved and the destination (Not working)
Param (
[string]$SMTPServer = "XXX.XX.xXX",
[string]$From = "from#email.com",
[string]$To = "to#email.com",
[string]$Subject = "New File"
)
Import-Csv -Path "C:\moveallfiles.csv" -ErrorAction Stop | foreach {
if (-not (Test-Path -Path $_.Destination))
{
# Create directory if needed
New-Item -ItemType directory -Path $_.Destination -WhatIf
}
# Copy file
$MoveFileprocess = Move-Item -Path $_.Source -Destination $_.Destination -force -PassThru
}
$SMTPMessage = #{
To = $To
From = $From
Subject = "$Subject"
Smtpserver = $SMTPServer
}
$SMTPBody = "`nThe following files have recently been added `n
$MoveFileprocess `n"
Send-MailMessage #SMTPMessage -Body $SMTPBody
Right now its listing all the files in one single line, hard to read
I was wondering if theres a way to send the body email as
File 1 moved to Destination 1
File 2 moved to Destination 2
and so on...
You were pretty close. Try this:
Param (
[string]$SMTPServer = "XXX.XX.xXX",
[string]$From = "from#email.com",
[string]$To = "to#email.com",
[string]$Subject = "New File"
)
$SMTPBody = "`nThe following files have recently been added `n"
Import-Csv -Path "C:\moveallfiles.csv" -ErrorAction Stop | foreach {
if (-not (Test-Path -Path $_.Destination)){
# Create directory if needed
New-Item -ItemType directory -Path $_.Destination
}
If((Get-Item $_.Source).LastWriteTime -gt (Get-Item $_.Destination).LastWriteTime){
Move-Item -Path $_.Source -Destination $_.Destination -force
$SMTPBody += "$($_.Source) moved to $($_.Destination)`n"
}Else{
Write-Warning "Skipped overwriting newer $($_.Destination)"
}
}
$SMTPMessage = #{
To = $To
From = $From
Subject = "$Subject"
Smtpserver = $SMTPServer
}
Send-MailMessage #SMTPMessage -Body $SMTPBody
Note that Move-Item -PassThru returns the affected object, not a string. I took out the -WhatIf parameter for the New-Item command, indented the foreach{} loop and made indenting and brackets consistent.

Renaming Variable Output in PowerShell

I'm trying to write a script to monitor URLs. I've managed to get the information I want, and my script looks like this
$logfile = "C:\LogFileTest.log"
Function WriteHeader-Websites {
[cmdletbinding()]
param (
[string]$URL
)
if ($logfile -eq "nologfile"){
write-host
write-host "$URL"
write-host
}
else {
add-content -path $logfile -value ""
add-content -path $logfile -value "$URL"
add-content -path $logfile -value ""
}
}
Function Test-Websites {
$URLs = Get-Content -Path C:\WebsiteChecks.txt
foreach ($URL in $URLs) {
$ArrayLine =$Line.split(",")
$
$Line.name =
try {
$request = [System.Net.WebRequest]::Create($URL)
$response = $request.GetResponse()
WriteHeader-Websites "$URL is available!"
} catch {
WriteHeader-Websites ("$URL failed! The error message was '{0}'." -f $_)
} finally {
if ($response) {
$response.Close()
Remove-Variable response
}
}
}
}
Test-Websites
My text file looks like this:
http://www.google.com
http://www.bing.com
http://www.bbc.co.uk
And the output from the script looks like this:
http://www.google.com is available!
I'd like to be able to add a short name for the websites in the text file, so it uses that name instead.
An example of the text file would be:
http://www.google.com,Google Website
http://www.bing.com,Bing Website
and I'd like the return to look like:
Google Website is available!
Bing Website is available!
or
Google Website failed! Error message is...
But I have no idea how to do that, or what I'd even Google for to find it out. Any suggestions?
Thanks
The easiest way to do this would be to create a csv with headers, then you can refer to the columns using their headers.
url,description
http://www.google.com,Google Website
http://www.bing.com,Bing Website
Then use Import-Csv to import the file, so you could do something like this:
Import-Csv -Path C:\WebsiteChecks.txt | % {
$_.description
$_.url
}
Update
Here's your code rewritten using Import-Csv notice I put description in it's own variable as $_ would be updated by the exception in your catch statement.
$logfile = "C:\LogFileTest.log"
Function WriteHeader-Websites {
[cmdletbinding()]
param (
[string]$URL
)
if ($logfile -eq "nologfile"){
write-host
write-host "$URL"
write-host
}
else {
add-content -path $logfile -value ""
add-content -path $logfile -value "$URL"
add-content -path $logfile -value ""
}
}
Function Test-Websites {
Import-Csv -Path C:\WebsiteChecks.txt | % {
$url = $_.url
$description = $_.description
try {
$request = [System.Net.WebRequest]::Create($url)
$response = $request.GetResponse()
WriteHeader-Websites "$description is available!"
} catch {
WriteHeader-Websites ("$description failed! The error message was '{0}'." -f $_)
} finally {
if ($response) {
$response.Close()
Remove-Variable response
}
}
}
}
Test-Websites

Powershell Check the folder structure exists or not

i am on a task to upgrading a batch file to powershell.. i have to perform a task that need to check if the folder structure is exist or not. if not create it from where it missing. Also need to send a mail using smtp about the status...
example:
D:\folder\folder1\folder2\folder3
E:\folder\folder1\folder2\folder3
E:\folderA\folderB\FolderC\FolderD\FolderE
if missing only FolderC and Rest create from FolderC\FolderD\FolderE
if missing from FolderB and rest create from FolderB\FolderC\FolderD\FolderE
This function will do what you need, including detailed log. Copy-paste it or save alongside with your script as Test-DirectoryTree.ps1 and use dot-sourcing to load:
$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path
. (Join-Path -Path $ScriptDir -ChildPath 'Test-DirectoryTree.ps1')
Usage:
# Array of paths to check
$Paths = #(
'D:\folder\folder1\folder2\folder3',
'E:\folder\folder1\folder2\folder',
'E:\folderA\folderB\FolderC\FolderD\FolderE'
)
# Store function output in $Log variable
# W\o "Create" switch function will only report missing directories
$Log = $Paths | Test-DirectoryTree -Create
# Send email
Send-MailMessage -SmtpServer 'mail.company.com' -From 'script#company.com' -To 'admin#company.com' -Subject 'Folder status' -Body $Log
Test-DirectoryTree function:
function Test-DirectoryTree
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()]
[string[]]$Paths,
[switch]$Create
)
Begin
{
# Set path separator
$Separator = '\'
# Init array to hold log
$Log = #()
}
Process
{
# For every path in array
foreach ($Path in $Paths){
# Init array to store existing paths
$Tree = #()
# Split path
foreach ($Dir in $Path.Split($Separator)){
# If not first element
if($Tree)
{
# Build path for current dir to check
$CurrDir = Join-Path -Path ($Tree -join $Separator) -ChildPath $Dir
}
else # If not first element
{
# Check if root dir exist
if(!(Test-Path -LiteralPath $Dir -PathType Container) -and [System.IO.Path]::IsPathRooted($Dir))
{
Write-Error "Root folder '$Dir' is not valid!"
break
}
else
{
# Build path for current dir to check
$CurrDir = $Dir
}
}
# If current dir not exist
if(!(Test-Path -LiteralPath $CurrDir -PathType Container))
{
# Write message to log
$Log += "Folder doesn't exist: $CurrDir"
# If we asked to create missing dirs
if($Create)
{
# Try to create dir
try
{
New-Item -ItemType Directory -Path $CurrDir -ErrorAction Stop | Out-Null
$Log += "Folder created: $CurrDir"
}
catch
{
$Log += "Failed to create folder: $CurrDir"
}
}
}
# If current dir exist, do nothing and add it to existing paths
$Tree += $Dir
}
}
}
End
{
# Return log
return $Log
}
}
This could be more beautiful but.. Here it goes, there are of course many ways to do this depending on exactly how you want to use it.
$SmtpServer = '192.168.1.2' #Replace this with IP adress to your SMTP-server
$Body='' #Two single quotation marks
$path = Read-Host 'Enter Path (Type Exit to quit)'
While ($path -ne 'Exit') {
IF (Test-Path $path) {
$Body += "$path already exists `n" #Adds $path to $Body and breaks line
}
ELSE {
New-Item $path -ItemType directory
$Body += "$path was created `n" #Adds $path to $Body and breaks line
}
$path = Read-Host 'Enter Path (Type Exit to quit)'
}
IF($Body -ne '') {
Send-Mailmessage -SmtpServer $SmtpServer -Subject "Folder status" -Body $Body
}

Read folder content and finds lastwritetime of today if finds file which was lastwritetime of today email it

I have a folder which a log is written to it any time a job fails, the folder contains all older logs as well. The goal is to send an email to user on daily basis with the log file generated. If the log found, sends the file in attachment, else sends an email stating file not found. I put below script but the problem is that it reads all files, even though there is only one log file for today.
$TodaysDate = (Get-Date).DayOfYear
$LogPath = "C:\Temp\Error"
$filename = Get-ChildItem -Path "C:\temp\Error"
foreach ($file in $LogPath) {
$fileDate = ($file.LastWriteTime).DayOfYear
if ($fileDate -eq $TodaysDate) {
$fileName = $file.Name
}
}
$LogFile = $LogPath + $filename
$LogFileName = $LogFile | ? {Test-Path $LogFile} | Get-ChildItem |
Where-Object { $_ -is [System.IO.FileInfo] }
$MessageBodyNA = "Email from task scheduler. No log file found" + $filename
$MessageBodyA = "Email from task scheduler. File" + $LogFileName +
" found and attached"
#
$FromAddress = "test#gmail.com"
#
$ToAddress = "test#gmail.com"
$Subject = "Bad log"
# SMTP server name
$SMTPserver = "smtp.test"
# check if attachment file exists if so email with attachment else without
# attachment
if ( $LogFileName | Where-Object { $_ -is [System.IO.FileInfo] }) {
send-mailmessage -from $FromAddress -to $ToAddress -subject $Subject `
-body $MessageBodyA -smtpServer $SMTPserver -Attachments $LogFileName
} else {
send-mailmessage -from $FromAddress -to $ToAddress -subject $Subject `
-body $MessageBodyNA -smtpServer $SMTPserver
}
Your routine to determine the file is totally convoluted. Change this:
$TodaysDate = (Get-Date).DayOfYear
$LogPath = "C:\Temp\Error"
$filename = Get-ChildItem -Path "C:\temp\Error"
foreach ($file in $LogPath) {
$fileDate = ($file.LastWriteTime).DayOfYear
if ($fileDate -eq $TodaysDate) {
$fileName = $file.Name
}
}
$LogFile = $LogPath + $filename
$LogFileName = $LogFile | ? {Test-Path $LogFile} | Get-ChildItem |
Where-Object { $_ -is [System.IO.FileInfo] }
into this:
$LogPath = "C:\Temp\Error"
$LogFileName = Get-ChildItem $LogPath | ? {
-not $_.PSIsContainer -and
$_.LastWriteTime.Date -eq (Get-Date).Date
}

Powershell start-job scriptblock not executing

I have some pretty simply code which aims to convert each file in a directory into HTML. My problem is that although a job is successfully created for each file, the scriptblock does not run, ever.
$convert = {
Param(
[parameter(ValueFromPipeline=$true)]
$file
)
$content = Get-Content -Path $file.FullName
$outDir= New-Item -Type dir -Path $file.FullName + "\HTMLFiles"
$outFile = $outDir + $file.Name + ".html"
foreach($line in $content) {
#move the content into a variable and add some html tags
$html = $html + '<tr>' + $line + '</tr>' +'<br>'
}
#convert the variable to .html and save the result as a file
ConvertTo-Html -Head $style -Body $html | Out-File -FilePath $outFile -Encoding "ASCII"
#empty the variable
$html = " "
}
Function main
{
Param(
[parameter(Position=0, Mandatory=$true, ValueFromPipeLine=$true)]
$target = $args[0]
)
#stores some html styling code
$path = $pwd.Path + "\style.txt"
$style = Get-Content -Path $path
#collect all files in the dirctory
$files = Get-ChildItem -Path $target -Recurse
foreach($file in $files) {
#for each file in the collection start a job which runs the given scriptblock (scriptblock is not working)
Start-Job -Name $file.name -ScriptBlock $convert -ArgumentList $file
}
#clean-up
Write-Host "Finished jobs"
Wait-Job *
Remove-Job -State Completed
}
main($args[0])
I'm fairly new to powershell and have played around with ways to solve this, but can't seem to figure it out.
Change: I removed the parameters from the script block and the function.
Why: because the argument is passed in by Start-Job, and also for the function via the syntax function name (argument1, argument2) {}.
I also took away from the brackets from the function call, because in Powershell you call functions like:
function "argument1" "argument2"
$convert = {
$content = Get-Content -Path $file.FullName
$outDir= New-Item -Type dir -Path $file.FullName + "\HTMLFiles"
$outFile = $outDir + $file.Name + ".html"
foreach($line in $content) {
#move the content into a variable and add some html tags
$html = $html + '<tr>' + $line + '</tr>' +'<br>'
}
#convert the variable to .html and save the result as a file
ConvertTo-Html -Head $style -Body $html | Out-File -FilePath $outFile -Encoding "ASCII"
#empty the variable
$html = " "
}
Function main ($args)
{
$target = $args
#stores some html styling code
$path = $pwd.Path + "\style.txt"
$style = Get-Content -Path $path
#collect all files in the dirctory
$files = Get-ChildItem -Path $target -Recurse
foreach($file in $files) {
#for each file in the collection start a job which runs the given scriptblock (scriptblock is not working)
Start-Job -Name $file.name -ScriptBlock $convert -ArgumentList $file
}
#clean-up
Write-Output "Finished jobs"
Wait-Job *
Remove-Job -State Completed
}
main $args[0]