Using the below code how do I put a check in place that if the $FileName doesn't exist in the email then fail.
Ideally I want something like this -
Write-Host "Attachment $FileName does not exist for download."; exit 1
But I'm not sure where to put it in the code.
$EmailwithAttachments = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service, $Email.Id, $emailProps)
Foreach ($File in $EmailwithAttachments.Attachments) {
If ($File.name -eq $FileName) {
Try {
#Just to be safe, remove any invaild characters from the attachment name
$FileName = $File.name -replace ("[{0}]" -f ([RegEx]::Escape([String][System.IO.Path]::GetInvalidFileNameChars())))
#Current can't handle Emails that are attacments, so we skip them
Write-Host "Attachment $FileName exists for download."
If ($File.contenttype -eq $null) {
Write-Warning "Attachment is an email, skipping"
$EMLAttachment = $TRUE
continue
}
Else {
$EMLAttachment = $FALSE
}
$File.load($SavePath + "\\" + $FileName)
If (($FileName -like "*.zip") -and $UnZipFiles) {$ZipFileNames += $SavePath + "\"+ $FileName}
}
Catch {
Send-ErrorReport -Subject "Attachment save path cannot be accessed" -body "Please verify the following path <B>$SavePath</B><BR>Full Error:<BR> $_" -HaltScript
}
}
}
If ($UnZipFiles) {
Try {
$shell = new-object -com shell.application
$Location = $shell.namespace($SavePath)
foreach ($ZipFile in $ZipFileNames) {
$ZipFolder = $shell.namespace($ZipFile)
$Location.Copyhere($ZipFolder.items())
Remove-item $ZipFile
}
}
Catch {
Send-ErrorReport -Subject "Attachment save path cannot be accessed" -body "Please verify the following path <B>$SavePath</B><BR>Full Error:<BR> $_" -HaltScript
}
}
}
}
$strFileName=($SavePath + "\\" + $FileName)
If (Test-Path $strFileName){
If ((Get-Content $strFileName) -eq "") {
"File Exists and is empty"; exit 1
}Else{
"File Exists and is stable."
}
}Else{
"File Does Not Exist"; exit 1
}
your $filename variable is empty, it should be declared before you can put it in your If statement, declare it before like this:
$EmailwithAttachments = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service, $Email.Id, $emailProps)
$Filename = MyFileName
Foreach ($File in $EmailwithAttachments.Attachments) {
If ($File.name -eq $FileName)
Related
I need to check that I have all files before running the rest of my powershell script.
What is the best way to go through the below code and after it has found each file required, continue with the rest of my script. Or if any of the files are not found, I send an email alert along with exiting the script.
# collect log one
if (Test-Path $logone) {
$one = Import-Csv -Path $logone
Write-Host "Log one found"
}
else {
#Send email "Log one not found!"
Write-Host "Log one not found!"
}
# collect log two
if (Test-Path -Path $logtwo) {
$two = Import-Csv -Path $logtwo
Write-Host "Log two found"
}
else {
#Send email "Log two not found!"
Write-Host "Log two not found!"
}
# collect log three data
if (Test-Path -Path $logthree) {
$three = Import-Csv -Path $logthree
Write-Host "Log three found"
}
else {
#Send email "Log three not found!"
Write-Host "Log three not found!"
}
Would I just add the following code below what I have:
if (Test-Path $logone) -and (Test-Path $logtwo) -and (Test-Path $logthree) {
# continue with the rest of my code
}
else {
Write-Host "Script exited with error"
Exit
}
Is there a cleaner way of doing this?
Based on I.T Delinquent's answer here an example of how to achieve this:
$logOne = "C:\Temp\log1"
$logTwo = "C:\Temp\log2"
$logThree = "C:\Temp\log3"
$mandatoryLogs = $logOne,$logTwo,$logThree
$errors = #()
$count = 0
$logs = #{}
foreach($log in $mandatoryLogs){
$count++
if(Test-Path $log){
$logs["log$count"] = Import-Csv -Path $log
Write-Host "Log $count found!"
}else{
$errors += "$log is missing"
}
}
if($errors){
$body = #"
Dear Admin,
The following errors occured: $($errors | Out-String)
Regards
"#
try{
Send-MailMessage -Body $body # -To -From etc etc
}catch{
throw $_
}
throw "Quiting because of errors, mail has been sent"
}else{
Write-Host "Continuing script" -ForegroundColor Green
}
Next you can access each of the logs in the following way:
$logs['log1']
Or:
Write-Host "$($logs['log3'].randomPropertyWhichExistsInTheCSV)"
Hope this helps!
I have this PowerShell script that I'm working on. CSV file is imported to get source and destination paths. The goal is to move files from a SFTP/FTP server into a destination and send an email report.
Task scheduler will run this code every hour. And if there's a new file, as email will be sent out.
It's almost done, but two things are missing:
Check if the file already exists and Body email seems empty: Getting the following error: Cannot validate argument on parameter 'Body'. The argument is null or empty. Provide an argument that is not
null or empty, and then try the command again.
I would like some assistance on how to check if the file exists and how to get this email if a new file was dropped and copied to the destination list
$SMTPBody = ""
$SMTPMessage = #{
"SMTPServer" = ""
"From" = ""
"To" = ""
"Subject" = "New File"
}
try {
# Load WinSCP .NET assembly
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::sftp
HostName = ""
UserName = ""
Password = ""
PortNumber = "22"
FTPMode = ""
GiveUpSecurityAndAcceptAnySshHostKey = $true
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Download files
$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
Import-Csv -Path "D:\FILESOURCE.csv" -ErrorAction Stop | foreach {
$synchronizationResult = $session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Local, $_.Destination, $_.Source, $False)
$synchronizationResult.Check()
foreach ($download in $synchronizationResult.Downloads ) {
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
$SMTPBody +=
"`n Files: $($download.FileName -join ', ') `n" +
"Current Location: $($_.Destination)`n"
Send-MailMessage #SMTPMessage -Body $SMTPBody
}
$transferResult =
$session.GetFiles($_.Source, $_.Destination, $False, $transferOptions)
#Find the latest downloaded file
$latestTransfer =
$transferResult.Transfers |
Sort-Object -Property #{ Expression = { (Get-Item $_.Destination).LastWriteTime }
} -Descending |Select-Object -First 1
}
if ($latestTransfer -eq $Null) {
Write-Host "No files found."
$SMTPBody += "There are no new files at the moment"
}
else
{
$lastTimestamp = (Get-Item $latestTransfer.Destination).LastWriteTime
Write-Host (
"Downloaded $($transferResult.Transfers.Count) files, " +
"latest being $($latestTransfer.FileName) with timestamp $lastTimestamp.")
$SMTPBody += "file : $($latestTransfer)"
}
Write-Host "Waiting..."
Start-Sleep -Seconds 5
}
finally
{
Send-MailMessage #SMTPMessage -Body $SMTPBody
# Disconnect, clean up
$session.Dispose()
}
}
catch
{
Write-Host "Error: $($_.Exception.Message)"
}
I believe your code has more problems than you think.
Your combination of SynchronizeDirectories and GetFiles is suspicious. You first download only the new files by SynchronizeDirectories and then you download all files by GetFiles. I do not think you want that.
On any error the .Check call will throw and you will not collect the error into your report.
You keep sending partial reports by Send-MailMessage in the foreach loop
This is my take on your problem, hoping I've understood correctly what you want to implement:
$SMTPBody = ""
Import-Csv -Path "FILESOURCE.csv" -ErrorAction Stop | foreach {
Write-Host "$($_.Source) => $($_.Destination)"
$SMTPBody += "$($_.Source) => $($_.Destination)`n"
$synchronizationResult =
$session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Local, $_.Destination, $_.Source, $False)
$downloaded = #()
$failed = #()
$latestName = $Null
$latest = $Null
foreach ($download in $synchronizationResult.Downloads)
{
if ($download.Error -eq $Null)
{
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
$downloaded += $download.FileName
$ts = (Get-Item $download.Destination).LastWriteTime
if ($ts -gt $latest)
{
$latestName = $download.FileName;
$latest = $ts
}
}
else
{
Write-Host "File $($download.FileName) download failed" -ForegroundColor Red
$failed += $download.FileName
}
}
if ($downloaded.Count -eq 0)
{
$SMTPBody += "No new files were downloaded`n"
}
else
{
$SMTPBody +=
"Downloaded $($downloaded.Count) files:`n" +
($downloaded -join ", ") + "`n" +
"latest being $($latestName) with timestamp $latest.`n"
}
if ($failed.Count -gt 0)
{
$SMTPBody +=
"Failed to download $($failed.Count) files:`n" +
($failed -join ", ") + "`n"
}
$SMTPBody += "`n"
}
It will give you a report like:
/source1 => C:\dest1`
Downloaded 3 files:
/source1/aaa.txt, /source1/bbb.txt, /source1/ccc.txt
latest being /source1/ccc.txt with timestamp 01/29/2020 07:49:07.
/source2 => C:\dest2
Downloaded 1 files:
/source2/aaa.txt
latest being /source2/aaa.txt with timestamp 01/29/2020 07:22:37.
Failed to download 1 files:
/source2/bbb.txt
To check and make sure the csv file exists before you process the entire thing, you can use the Test-Path,
...
if (!(Test-Path D:\FileSource.csv)) {
Write-Output "No File Found"
$SMTPBody += "There are no new files at the moment"
return; # Dont run. file not found. Exit out.
}
Import-Csv -Path "D:\FILESOURCE.csv" -ErrorAction Stop | foreach {
...
and for the Body error you are getting, it is coming from the finally loop because there are cases where $SMTPBody would be null. This will no longer be an issue because $SMTPBody will have some text when file is not found at the beginning.
Even though you are using return in the if statement to check if the file exists, finally will always get executed. Since we updated $smtpbody, your Send-MailMessage will no longer error out.
Update
If you want to check if the file you are downloading already exists, you can use the if statement like this,
foreach ($download in $synchronizationResult.Downloads ) {
if (!(Test-Path Join-Path D: $download.FileName) {
$SMTPBody += "File $($download.Filename) already exists, skipping."
continue # will go to the next download...
}
Write-Host "File $($download.FileName) downloaded" -ForegroundColor Green
...
If you do get the error regarding body, thats mostly because your script came across an exception and was sent straight over to finally statement. Finally statement sends the email with empty body because it was never set (due to exception). I would recommend using the debugger (step through) and see which step causes the exception and look into adding steps to make sure script doesnt fail.
I've written a function to check if an excel file is being used/locked by another process/user in a shared network drive, and if used, to pause the script and keep checking till it's available as the next action is to move it out of it's folder. However when I'm using System.IO to read the file, it does not open the file. I've tested on my local drive and this does open the file, but does this not work in Network Drives?
$IsLocked = $True
Function Test-IsFileLocked {
[cmdletbinding()]
Param (
[parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName','PSPath')]
[string[]]$Path
)
Process {
while($isLocked -eq $True){
If ([System.IO.File]::Exists($Path)) {
Try {
$FileStream = [System.IO.File]::Open($Path,'Open','Write')
$FileStream.Close()
$FileStream.Dispose()
$IsLocked = $False
} Catch {
$IsLocked = $True
echo "file in use, trying again in 10 secs.."
Start-Sleep -s 10
}
}
}
}
}
This is where the code does not pick up/open the excel file in my function
$FileStream = [System.IO.File]::Open($Path,'Open','Write')
This is where the program calls the function.Loop through a folder of items in the network drive and if the item matches the pattern, then the function will be called to check if the file is in use:
$DirectoryWithExcelFile = Get-ChildItem -Path "Z:\NetworkDriveFolder\"
$DestinationFolder = "Z:\DestinationFolder\"
$pattern = "abc"
foreach($file in $DirectoryWithExcelFile){
if($file.Name -match $pattern){
Test-IsFileLocked -Path $file
$destFolder = $DestinationFolder+$file.Name
Move-item $file.FullName -destination $destFolder
break
}
}
You have to place the close and dispose in the finally part of the try so if it throws an exception it disposes of the lock. There's no guarantee it's a file lock exception either so you are better to throw the exception, catch the exception you're expecting or write it out
$IsLocked = $True
Function Test-IsFileLocked {
[cmdletbinding()]
Param (
[parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName','PSPath')]
[string]$Path
)
Process {
while($isLocked -eq $True){
If ([System.IO.File]::Exists($Path)) {
Try {
$FileStream = [System.IO.File]::Open($Path,'Open','Write')
$IsLocked = $False
} Catch [System.IO.IOException] {
$IsLocked = $True
echo "file in use, trying again in 10 secs.."
Start-Sleep -s 10
} Catch {
# source https://stackoverflow.com/questions/38419325/catching-full-exception-message
$formatstring = "{0} : {1}`n{2}`n" +
" + CategoryInfo : {3}`n" +
" + FullyQualifiedErrorId : {4}`n"
$fields = $_.InvocationInfo.MyCommand.Name,
$_.ErrorDetails.Message,
$_.InvocationInfo.PositionMessage,
$_.CategoryInfo.ToString(),
$_.FullyQualifiedErrorId
$formatstring -f $fields
write-output $formatstring
} finally {
if($FileStream) {
$FileStream.Close()
$FileStream.Dispose()
}
}
}
}
}
}
Edit:
Path should be a string, not a string array unless you have multiple paths in which case rename to $Paths and loop through them $Paths| % { $Path = $_; #do stuff here }
I have the following PowerShell script to run a quick HitmanPro scan on computers. I have deployed this script through GFI RMM and while it works, I need to pass it domain administrator information.
$HitmanParameters = "/scanonly /quick /noinstall /quiet"
$AgentPath = (${env:ProgramFiles(x86)}, ${env:ProgramFiles} -ne $null)[0]
if (Test-Path "$AgentPath\Advanced Monitoring Agent") {
$AgentPath="$AgentPath\Advanced Monitoring Agent"
} elseif (Test-Path "$AgentPath\Advanced Monitoring Agent GP") {
$AgentPath="$AgentPath\Advanced Monitoring Agent GP"
} else {
Write-Host "Agent Path Not Found"
}
if (-not (Test-Path "$AgentPath\HitmanPro")) {
Write-Host "Creating Hitman Folder"
[system.io.directory]::CreateDirectory("$AgentPath\HitmanPro")
}
$HitmanParameters = "$HitmanParameters /log=""$AgentPath\HitmanPro\results.txt"""
if (-not (Test-Path "$AgentPath\HitmanPro\HitmanPro.exe")) {
Write-Host "HitmanPro Not found"
if ($(Get-WmiObject -Query "SELECT * FROM Win32_Processor WHERE AddressWidth='64'")) {
$HMPURL = "http://dl.surfright.nl/HitmanPro_x64.exe"
} else {
$HMPURL = "http://dl.surfright.nl/HitmanPro.exe"
}
Write-Host "Downloading $HMPURL ..."
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile($HMPURL,"$AgentPath\HitmanPro\HitmanPro.exe")
if (Test-Path "$AgentPath\HitmanPro\HitmanPro.exe") {
Write-Host "Download Complete!"
} else {
Write-Host "Error Downloading Hitman Pro"
}
}
if (Test-Path "$AgentPath\HitmanPro\HitmanPro.exe") {
Write-Host "Running HitmanPro Scan..."
Write-Host "HitmanPro.exe $HitmanParameters"
Start-Process -FilePath "$AgentPath\HitmanPro\HitmanPro.exe" -ArgumentList "$HitmanParameters" -NoNewWindow -Wait
If (Test-Path "$AgentPath\HitmanPro\results.txt") {
$data = Get-Content -Encoding Unicode "$AgentPath\HitmanPro\results.txt"
remove-item "$AgentPath\HitmanPro\results.txt"
foreach ($line in $data) {
If($line.contains("Threats")) {
$Infections = [int] $line.Split(":")[1]
If($Infections -eq 0) {
Write-Host "No Infections Found"
$data
exit 0
break
} Else {
Write-Host "Infections Found!"
$data
exit 1000
break
}
} #If Line contains Threats
} #ForEach
} Else {
Write-Host "No Logfile Found"!
} #LogFile Found
} Else {
Write-Host "HitmanPro Still Not Found!"
}#HitmanPro Found
I know a lot has been covered on zipping files using Powershell however I cannot find a method which does exactly what I need.
I want to be able to zip folders AND files into a .zip folder without having the parent folder inside the zip. So for example I have a folder named STUFF which contains files/folders, I want to zip this into a folder called STUFF.zip. This folders structure would then be STUFF.zip>files/folders NOT STUFF.zip>STUFF>files/folders as I currently get using this code...
function CountZipItems(
[__ComObject] $zipFile)
{
If ($zipFile -eq $null)
{
Throw "Value cannot be null: zipFile"
}
Write-Host ("Counting items in zip file (" + $zipFile.Self.Path + ")...")
[int] $count = CountZipItemsRecursive($zipFile)
Write-Host ($count.ToString() + " items in zip file (" `
+ $zipFile.Self.Path + ").")
return $count
}
function CountZipItemsRecursive(
[__ComObject] $parent)
{
If ($parent -eq $null)
{
Throw "Value cannot be null: parent"
}
[int] $count = 0
$parent.Items() |
ForEach-Object {
$count += 1
If ($_.IsFolder -eq $true)
{
$count += CountZipItemsRecursive($_.GetFolder)
}
}
return $count
}
function IsFileLocked(
[string] $path)
{
If ([string]::IsNullOrEmpty($path) -eq $true)
{
Throw "The path must be specified."
}
[bool] $fileExists = Test-Path $path
If ($fileExists -eq $false)
{
Throw "File does not exist (" + $path + ")"
}
[bool] $isFileLocked = $true
$file = $null
Try
{
$file = [IO.File]::Open(
$path,
[IO.FileMode]::Open,
[IO.FileAccess]::Read,
[IO.FileShare]::None)
$isFileLocked = $false
}
Catch [IO.IOException]
{
If ($_.Exception.Message.EndsWith(
"it is being used by another process.") -eq $false)
{
Throw $_.Exception
}
}
Finally
{
If ($file -ne $null)
{
$file.Close()
}
}
return $isFileLocked
}
function GetWaitInterval(
[int] $waitTime)
{
If ($waitTime -lt 1000)
{
return 100
}
ElseIf ($waitTime -lt 5000)
{
return 1000
}
Else
{
return 5000
}
}
function WaitForZipOperationToFinish(
[__ComObject] $zipFile,
[int] $expectedNumberOfItemsInZipFile)
{
If ($zipFile -eq $null)
{
Throw "Value cannot be null: zipFile"
}
ElseIf ($expectedNumberOfItemsInZipFile -lt 1)
{
Throw "The expected number of items in the zip file must be specified."
}
Write-Host -NoNewLine "Waiting for zip operation to finish..."
Start-Sleep -Milliseconds 1000 # ensure zip operation had time to start
[int] $waitTime = 0
[int] $maxWaitTime = 60 * 1000 # [milliseconds]
while($waitTime -lt $maxWaitTime)
{
[int] $waitInterval = GetWaitInterval($waitTime)
Write-Host -NoNewLine "."
Start-Sleep -Milliseconds $waitInterval
$waitTime += $waitInterval
Write-Debug ("Wait time: " + $waitTime / 1000 + " seconds")
[bool] $isFileLocked = IsFileLocked($zipFile.Self.Path)
If ($isFileLocked -eq $true)
{
Write-Debug "Zip file is locked by another process."
Continue
}
Else
{
Break
}
}
Write-Host
If ($waitTime -ge $maxWaitTime)
{
Throw "Timeout exceeded waiting for zip operation"
}
[int] $count = CountZipItems($zipFile)
If ($count -eq $expectedNumberOfItemsInZipFile)
{
Write-Debug "The zip operation completed succesfully."
}
ElseIf ($count -eq 0)
{
Throw ("Zip file is empty. This can occur if the operation is" `
+ " cancelled by the user.")
}
ElseIf ($count -gt $expectedCount)
{
Throw "Zip file contains more than the expected number of items."
}
}
function ZipFolder(
[IO.DirectoryInfo] $directory)
{
If ($directory -eq $null)
{
Throw "Value cannot be null: directory"
}
Write-Host ("Creating zip file for folder (" + $directory.FullName + ")...")
[IO.DirectoryInfo] $parentDir = $directory.Parent
[string] $zipFileName
If ($parentDir.FullName.EndsWith("\") -eq $true)
{
# e.g. $parentDir = "C:\"
$zipFileName = $parentDir.FullName + $directory.Name + ".zip"
}
Else
{
$zipFileName = $parentDir.FullName + "\" + $directory.Name + ".zip"
#$zipFileName = $directory.Name + ".zip"
#$zipFileName = $parentDir.FullName + ".zip"
}
If (Test-Path $zipFileName)
{
Throw "Zip file already exists ($zipFileName)."
}
Set-Content $zipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
$shellApp = New-Object -ComObject Shell.Application
$zipFile = $shellApp.NameSpace($zipFileName)
If ($zipFile -eq $null)
{
Throw "Failed to get zip file object."
}
[int] $expectedCount = (Get-ChildItem $directory -Force -Recurse).Count
$expectedCount += 1 # account for the top-level folder
$zipFile.CopyHere($directory.FullName)
#Get-ChildItem $directory | foreach {$zipFile.CopyHere($_.fullname)}
# wait for CopyHere operation to complete
WaitForZipOperationToFinish $zipFile $expectedCount
Write-Host -Fore Green ("Successfully created zip file for folder (" `
+ $directory.FullName + ").")
}
Usage
Remove-Item "H:\STUFF.zip"
[IO.DirectoryInfo] $directory = Get-Item "H:\STUFF"
ZipFolder $directory
Complete credit goes here for this code. I really appreciate any help I get, this capability is crucial to my project! Unfortunately I cannot use the Community Extension module as other PC's this will be run on does not have this module installed.
Thanks!
As per #mjolinor "H:\Stuff\", if you have .NET 4.5 installed you can run the following:
$src = "H:\Stuff\"
$dst = "H:\Stuff.zip"
[Reflection.Assembly]::LoadWithPartialName( "System.IO.Compression.FileSystem" )
[System.IO.Compression.ZipFile]::CreateFromDirectory($src, $dst)
New solution available with WMF 5 (well not that new ^^)
Compress-Archive -Path H:\stuff\* -DestinationPath H:\stuff.zip -Force
If you use PSCX Module you will use the line code below to obtain what you need:
write-zip h:\Stuff\* myzipfile.zip
I'm developing a simple module in Powershell, to more details https://github.com/julianosaless/powershellzip