Problem:
I need a PowerShell script that checks each file in a folder for a specific character (#), then emails the filename only if the character was found.
What I have: I'm still new to loops/testing logic and not sure where it would fit in. My current script successfully checks for the bad character, but emails regardless if something was found or not.
$scr = dir \\fileshare\businessfolder\filedrop | Select-String '#'
$FromAddress = "myself#something.com"
$ToAddress = "myself#something.com"
$MessageSubject = "Bad Character Detected"
$SendingServer = "mail.something.com"
$SMTPMessage = New-Object System.Net.Mail.MailMessage $FromAddress, $ToAddress, $MessageSubject,$scr
$SMTPClient = New-Object System.Net.Mail.SMTPClient $SendingServer
$SMTPClient.Send($SMTPMessage)
This currently outputs in an email:
\fileshare\businessfolder\filedrop\AD060101.107:3:#N/A
\fileshare\businessfolder\filedrop\AD060102.107:3:#N/A
\fileshare\businessfolder\filedrop\AD060101.103:14:#N/A
The files I'm scanning through are formatted in this way:
AHMMDDNN.LLL
ADMMDDNN.LLL
The AH/AD indicates if it's the header or detail, the MMDD is month/day, and NN is if it's the first generated file for the day, second generated file, et cetera. LLL is the location number.
What I really want:
To take it a step further, I would like to delete the bad file header & detail file and email the extension to the business unit.
The process would be:
Scan for '#' in all files in the folder.
Delete the ADMMDDNN.LLL and corresponding AHMMDDNN.LLL file. (Example: AD060101.107 had a '#' in it, so delete that and the AH060101.107 file too)
Send an email stating "location LLL has been deleted, please recreate file". (Example: "Location 107 has been deleted, please recreate file")
Just iterate over all matches from the Select-String cmdlet and use a regex to create a pattern for the Remove-Item (alias rm) cmdlet like A[D|H]060101.103:
$filesToDelete = Select-String -Path '\\fileshare\businessfolder\filedrop\*' -Pattern '#'
$filesToDelete | ForEach-Object {
'location {0} has been deleted, please recreate file' -f ($_.Filename -split '\.')[-1]
$_.Path -replace '(.*\\A).(.*)', '$1[D|H]$2' | rm -Force -ea 0
}
Note: I omit the E-Mail part but added the message to the foreach.
Related
I am new to Powershell
I am creating a file with output in it and then search that file for a specific phrase and if it contains it, get sent an email. I've been able to create the file and then filter out what I need using sls but I can't seem to figure out how to get that file sent to me via email if it contains a specific word.
Ex - If the file contains the word Offline send that file attached to an email.
These are the following commands I've run so far -
d:
set-location -Path "program files\veritas\volmgr\bin"
.\vmoprcmd >d:\test.data\mediastatus.txt
cd \
set-location -Path "test.data"
sls offline .\mediastatus.txt
So in your situation-
you have output that's generated by this vmoprcmd executable
it's redirected to a file
you want to detect whether the file/output contains the string "Offline"
if it does, trigger an email
To achieve this, you can utilize the Select-String and Send-MailMessage cmdlets:
$Output = 'D:\test.data\mediastatus.txt'
& 'D:\Program Files\veritas\volmgr\bin\vmoprcmd.exe' > $Output
if (Select-String -Pattern offline -Path $Output -Quiet) {
$MailArgs = #{
'To' = 'mailaddress#example.com'
'From' = 'mymailbot#example.com'
'Subject' = 'Device offline!'
'Attachments' = $Output
'Body' = 'Whatever you want it to be'
'SmtpServer' = 'my.smtp.server.com'
'Port' = 25
}
Send-MailMessage #MailArgs
}
Documentation:
Call operator &
Select-String
Send-MailMessage
#Splatting
I have a script that I have put together, for the most part the script does what I want it to do, it hit's a list of servers, looking for log files that are 25+ hours old (indicating that another script isn't doing it's job), this worked perfectly in testing(1 to 5 servers), however once I turned it loose on the 150+ servers I want to check on in this environment, the file size increased, and the email process failed due to the fact the filesize is in excess of 10mb.
So now I need a way to compress the results, I would like to use 7zip, but for some reason I just cannot wrap my head around how to accomplish what I'm trying to do.
Any assistance would be greatly appreciated.
Here is the script I have thus far.
# Specify where the list of servers is located.
$SL = get-content C:\Scripts\lists\AgingLogsServers.txt
# Define logfile age to report on.
$limit = (Get-Date).AddHours(-25)
# Define the current date & time.
$filedate = get-date -f "MM.dd.yy_hh.mm.ss"
$emldate = get-date -f "MM.dd.yy"
# Variable to add current date & time to saved filename.
$filename = "AgingReport_$($filedate).log"
# Files or patterns to exclude from the scan.
$excluded = #(".exe")
# Specify SMTP server
$smtpserver = "mail.yourserver.com"
# Loop to process each server in the pool.
Foreach ($Server in $SL){
$c1++
Write-Progress -Activity 'Looking for Logfiles in excess of 25 hours old' -Status "Processing $($c1) of $($SL.count)" -CurrentOperation $Server -PercentComplete (($c1/$SL.count) * 100)
If (Test-Path "\\$Server\logs") {$SP1 = "\\$Server\Logs"}
Else {$SP1 = "\\$Server\D-Logs"}
Get-ChildItem -ErrorAction SilentlyContinue -Path $SP1 -Exclude $excluded -Include *.zip, *.7z, *.log -Recurse | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Foreach-Object {write-output $_.CreationTime $server $_.fullname} | Out-file C:\Scripts\data\$filename -Append -Width 160
}
# Zip the $filename and remove the original
# And this is where I believe a 7zip process would go to compress the result file, then I can reference that file and path in the Send-MailMessage line.
# Email the results.
Send-MailMessage -From "Aging.Logs#yourhost.com" -To "user#yourhost.com" -Subject "Aging Logs report for $emldate" -Body "Attached is the listing of aging logs for the environment for $emldate" -SmtpServer $smtpserver -Attachments C:\Scripts\data\$filename
# Give the logfile time to release from the email process.
Start-Sleep -s 120
# Clean up the results file.
#Remove-Item C:\Scripts\data\AgingReport*
Running 7-Zip is pretty easy. The syntax is
7z.exe a <archive path> <file to zip path>
That's easy, we just need to know where 7z.exe is. So, we'll make PowerShell find that, then execute it using the call operator &, with those parameters (by the way, the 'a' means that we're adding a file to an archive). Then we clean up the source file, and email the archive.
# Zip the $filename and remove the original
# Find 7-Zip executable
$7Zip = gci c:\Program* -include '7z.exe' -recurse -ea 4|select -first 1 -expand fullname
# Define archive name and path
$ZipFile = "C:\Scripts\data\$filename" -replace "...$","zip"
# Perform Zip
& $7Zip a "$ZipFile" "C:\Scripts\data\$filename" | Out-Null
# Remove source file
Remove-Item -Path "C:\Scripts\data\$filename"
# Email the results.
Send-MailMessage -From "Aging.Logs#yourhost.com" -To "user#yourhost.com" -Subject "Aging Logs report for $emldate" -Body "Attached is the listing of aging logs for the environment for $emldate" -SmtpServer $smtpserver -Attachments $ZipFile
Your archive, by the way, will be the same as your log file, but with a .zip file extension instead of .log.
#TheMadTechnician, Thank you for your very helpful post, I attempted to integrate what you gave me, but no matter how I went about it I was unable to get the desired action, I took the direction that you provided and finally was able to get it to work, here is the code that does everything that I wanted it to do, in-case anyone else is looking to accomplish the same thing.
<#
Script: AgingLogQuery.ps1
Author: Xander J.
Date: 11/12/2015
Aging log query checks the logs and d-logs shares contained within a text file to see if there are any logfiles older than 25
hours old,if it finds a logfile that is older than 25 hours old it passes the server name, the full path and filename and the
files age to the AgingReport log file.
After checking all of the servers in the list, the script archives the logfile, removes the original logfile, emails the
archive as an attachment, then waits a specified amount of time to remove the archive file.
#>
# Specify where the list of servers is located.
$SL = get-content C:\Scripts\lists\AgingLogsServers.txt
# Define logfile age to report on.
$limit = (Get-Date).AddHours(-25)
# Define the current date & time.
$filedate = get-date -f "MM.dd.yy_hh.mm.ss"
$emldate = get-date -f "MM.dd.yy"
# Variable to add current date & time to saved filename.
$filename = "AgingReport_$($filedate).log"
# Files or patterns to exclude from the scan.
$excluded = #("*.exe*")
# Specify SMTP server
$smtpserver = "mail.email.com"
#Get script path
$ScriptPath = Split-Path -Path $($MyInvocation.MyCommand.Definition)
#Get the path for 7za.exe
$zipexe = $ScriptPath + "\7za.exe"
set-alias sz $zipexe
$archive = "AgingReport_$($filedate).zip"
# Loop to process each server on the list.
Foreach ($Server in $SL){
$c1++
Write-Progress -Activity 'Looking for Logfiles in excess of 25 hours old' -Status "Processing $($c1) of $($SL.count)" -CurrentOperation $Server -PercentComplete (($c1/$SL.count) * 100)
If (Test-Path "\\$Server\logs") {$SP1 = "\\$Server\Logs"}
Else {$SP1 = "\\$Server\D-Logs"}
Get-ChildItem -ErrorAction SilentlyContinue -Path $SP1 -Exclude $excluded -Include *.zip, *.7z, *.log -Recurse | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Foreach-Object {write-output $_.CreationTime $server $_.fullname} | Out-file C:\Scripts\data\$filename -Append -Width 160
}
# Zip the $filename
& sz a -mmt -tzip c:\Scripts\Data\$archive C:\Scripts\data\AgingReport*.log -stl
# Clean up the results file.
Remove-Item -Force C:\Scripts\data\$filename
# Email the results.
Send-MailMessage -From "Aging.Logs#echopass.com" -To "user#email.com" -Subject "Aging Logs report for $emldate" -Body "Attached is the listing of aging logs for the environment for $emldate" -SmtpServer $smtpserver -Attachments C:\Scripts\data\$archive
# Give the logfile time to release from the email process.
Start-Sleep -s 15
# Clean up the results file.
Remove-Item -Force C:\Scripts\data\$archive
$log = "C:\bobloblawslawblog.log"
Get-Content $log |
? {$_.Modified -le $(Get-Date).AddHours(-4)} |
Select-String "TCP" -Context 1 |
% { #($_.Context.PreContext) + #($_.Line)}
{
$Workstation = hostname
$emailSmtpServer = "mail.server"
$emailFrom = "Error CODE 1234#$Workstation.com"
$emailTo = "my.name#my.company.com"
$emailSubject = "$Workstation Error CODE 1234"
$emailBody = "$Workstation experiencing Error Code 1234"
Send-MailMessage -To $emailTo -From $emailFrom -Subject $emailSubject -Credential $credentials -Body $emailBody -SmtpServer $emailSmtpServer
$credentials= New-Object System.Management.Automation.PSCredential ("username", $secpasswd)
}
I cannot seem to get this to grab the error from just the past 4hrs. The script grabs all the matching codes and sends them along.
The format of the timestamp in the log file is:
[2015-09-01 03:12:34,774] INFO com.server.mobilize.jte.service.listener.DesktopClientTransferListener(147) exception (Transport Protocol Seeker)- 218362: a warning or retriable exception has occurred Transfer warning: Error connecting to TCP Server-A/127.0.0.1:49221 relayed via Sever-B/External-IP:49221: Connection lost on tcp port read
I don't know what those last 3 numbers '975' indicate. It's not a pid.
What I want to do is have the script scrape a .log file - return a match from the last 4 hours and email an alert.
Ideally, I'd like the email body to include the actual error from the log.
Like I was saying in the comments you are trying access properties that don't exist in the pipeline. Get-Content returns a string array. Keep in mind that those whiles string do have special properties like linenumber there is no magical way that it interprets the date at the beginning. That is up to us.
Get-Content $log | ForEach-Object{
# Extract the date
$date = $null # Assume null
# Match the data that occurs after the bracket and before the comma. This can only be at the beginning of the line.
If($_ -match "^\[(.*?),"){
$date= [datetime]$matches[1]
}
# Build a new object as pass it along the pipe
New-Object -TypeName psobject -Property #{
Modified = $date
Line = $_
}
}
Many ways to do this but I opted for creating a new object where we give it a modified property. We use regex to extract the date from the beginning of the line and cast it to a [datetime] so that you can now perform date calculations.
Assuming the rest of your logic does what you want it to you just need to append it after the last } in my snippet above.
Something a little better
Making another object seemed like a waste of resources so I used the same logic in the Where instead. I have also added the Select-String portion of you code as well. I made some changes.
$results = Get-Content $log | Where-Object{
# Match the data that occurs after the bracket and before the comma. This can only be at the beginning of the line.
$date = $null
If($_ -match "^\[(.*?),"){$date = [datetime]$matches[1]}
$date -gt (Get-Date).AddHours(-4)
} | Select-String "tcp" -Context 1 | ForEach-Object{
"{0}`r`n{1}`r`n{2}" -f ($_.Context.Precontext -join "`r`n"), $_.line, ($_.context.postcontext -join "`r`n")
}
Now results will have just that. So you would see in there all entries that have occured in the last 4 hours that have the string "TCP" somewhere on the line. It will also include the line before and after the match. $results has the potential to be an array so you would need to account for that in your email. A simple $results -join "`r`n" would do it.
I've been putting "tags" into the names of files, but that's a terrible way of organizing a large number of files.
ex: "ABC - file name.docx"
So, I want to set the category attribute to "ABC" instead of having it in the name using PowerShell. The script would have to find all of the files with "ABC" in its name in the subdirectories of a certain folder and set the category attribute to "ABC".
So I have the first part where I am finding the files but I don't know where to go from here.
Get-ChildItem -Filter "ABC*" -Recurse
Any ideas?
Thanks.
So this borrow heavily from the Scripting Guys. What we need to do is for every file we find use the a Word COM object to access those special properties of the file. Using the current file name we extract the "category" by splitting on the first hyphen and saving both parts. First becomes the category and second is the new name we give the file assuming the category update was successful.
There is still margin for error with this but this
$path = "C:\temp"
# Create the Word com object and hide it sight
$wordApplication = New-Object -ComObject word.application
$wordApplication.Visible = $false
# Typing options for located Word Documents. Mainly to prevent changes to timestamps
$binding = "System.Reflection.BindingFlags" -as [type]
# Locate Documents.
$docs = Get-childitem -path $Path -Recurse -Filter "*-*.docx"
$docs | ForEach-Object{
$currentDocumentPath = $_.fullname
$document = $wordApplication.documents.open($currentDocumentPath)
$BuiltinProperties = $document.BuiltInDocumentProperties
$builtinPropertiesType = $builtinProperties.GetType()
$categoryUpdated = $false # Assume false as a reset of the state.
# Get the category from the file name for this particular file.
$filenamesplit = $_.Name.split("-",2)
$category = $filenamesplit[0].Trim()
# Attempt to change the property.
Try{
$BuiltInProperty = $builtinPropertiesType.invokemember("item",$binding::GetProperty,$null,$BuiltinProperties,"Category")
$BuiltInPropertyType = $BuiltInProperty.GetType()
$BuiltInPropertyType.invokemember("value",$binding::SetProperty,$null,$BuiltInProperty,[array]$category)
$categoryUpdated = $true
}Catch [system.exception]{
# Error getting property so most likely is not populated.
Write-Host -ForegroundColor Red "Unable to set the 'Category' for '$currentDocumentPath'"
}
# Close the document. It should save by default.
$document.close()
# Release COM objects to ensure process is terminated and document closed.
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($BuiltinProperties) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($document) | Out-Null
Remove-Variable -Name document, BuiltinProperties
# Assuming the category was successfully changed lets remove the information from the current filename as it is redundant.
If($categoryUpdated){Rename-Item $currentDocumentPath -NewName $filenamesplit[1].Trim()}
}
$wordApplication.quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($wordApplication) | Out-Null
Remove-Variable -Name wordApplication
[gc]::collect()
[gc]::WaitForPendingFinalizers()
You should see some explanation in comments that I tried to add for clarification. Also read the link above to get more of an explanation as to what is happening with the COM object.
I have a ZIP file generated with dynamic information (Report_ PC Name-Date_User). However when I go to attach the file I'm unable to use a wildcard. There is only one ZIP file in this directory so using a wildcard will not attach any other ZIP files.
#Directory storage
$DIR = "$ENV:TEMP"
#Max number of recent screen captures
$MAX = "100"
#Captures Screen Shots from the recording
$SC = "1"
#Turn GUI mode on or off
$GUI = "0"
#Caputres the current computer name
$PCName = "$ENV:COMPUTERNAME"
#Use either the local name or domain name
#$User = "$ENV:UserDomainName"
$User = "$ENV:UserName"
#Timestamp
$Date = Get-Date -UFormat %Y-%b-%d_%H%M
#Computer Information
$MAC = ipconfig /all | Select-String Physical
$IP = ipconfig /all | Select-String IPv4
$DNS = ipconfig /all | Select-String "DNS Servers"
#Needed to add space after user input information
$EMPT = "`n"
#Quick capture of the computer information
$Info = #"
$EMPT
*** COMPUTER INFORMATION ***
$PCName
$IP
$MAC
$DNS
"#
# Used to attach to the outlook program
$File = Get-ChildItem -Path $Dir -Filter "*.zip" | Select -Last 1 -ExpandProperty Fullname
$Start_Click = {
psr.exe /start /output $DIR\$Date-$PCName-$User.zip /maxsc $MAX /sc $SC /gui $GUI
}
$Stop_Click={
psr.exe /stop
}
$Email_Click = {
$Outlook = New-Object -Com Outlook.Application
$Mail = $Outlook.CreateItem(0)
$Mail.To = "deaconf19#gmail.com"
$Mail.Subject = "Capture Report from " + $PCName + " " + $User + " " + $Date
$Mail.Body = $Problem.text + $Info
$Mail.Attachments.Add($File)
$Mail.Send()
}
I no longer get an error but the file will not attach the first time around. The second time it will attach but it does the previous .zip not the most recent. I added my entire code
As per the msdn article it shows what the source needs to be which is.
The source of the attachment. This can be a file (represented by the
full file system path with a file name) or an Outlook item that
constitutes the attachment.
Which mean that it does not accept wildcards. To get around this you should instead use Get-ChildItem to return the name of your zip.
$File = Get-ChildItem -Path $Dir -Filter "*.zip" | Select -First 1 -ExpandProperty Fullname
That should return the full path to the first zip. Since Get-ChildItem returns and object we use -ExpandProperty on the Fullname so that you just return the full path, as a string, to the file. -First 1 is not truly required if you really only have the one file. On the off-chance you do including -First 1 will make sure only one file is attached.
Update from comments
I see that you are having issues with attaching a file still. My code would still stand however you might be having an issue with your .zip file or $dir. After where $file is declared I would suggest something like this:
If (! Test-Path $file){Write-Host "$file is not a valid zip file"}
If you would prefer, since I don't know if you see your console when you are running your code, you could use a popup