$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.
Related
I'm working on something that is extracting information from my desktop Outlook application. It works for most of the folders I've tried it on, but for some that have nearly a decade of e-mails, I get a "Exception getting 'ReceivedTime': 'Insufficient memory to continue the execution of the program." This is what I'm trying:
# New Outlook object
$ol = new-object -comobject "Outlook.Application";
# MAPI namespace
$mapi = $ol.getnamespace("mapi");
# Folder/Inbox
$folder = $mapi.Folders.Item('name#email.com').Folders.Item('Inbox')
# Sort by the Received Time
$contents = $folder.Items | sort ReceivedTime
# Get the first element in the array, convert to JSON, and then output to file
echo $contents[0] | convertTo-Json | Out-File C:\Users\ME\outlook_1.json -Encoding UTF8
Is there a better way of approaching this? I'm on Powershell 5.1.
EDIT: I've also tried this, which is looping through the array and then breaking on the first instance, but received the same error:
# New Outlook object
$ol = new-object -comobject "Outlook.Application";
# MAPI namespace
$mapi = $ol.getnamespace("mapi");
# Folder/Inbox
$folder = $mapi.Folders.Item('name#email.com').Folders.Item('Inbox')
# Sort by the Received Time
$contents = $folder.Items | sort ReceivedTime
$i = 1
foreach($item in $contents){
if (-not ([string]::IsNullOrEmpty($item))){
echo $item | convertTo-Json | Out-File Out-File C:\Users\ME\outlook_1.json -Encoding UTF8-Encoding UTF8
Break
}
}
Sort the items collection using Items.Sort("ReceivedTime", false), then read the first item using Items(1).
Make sure you store Items collection in a variable instead of accessing MAPIFolder.Items multiple times, otherwise you will get a brand new Items object every time you do that.
EDIT: I'm the OP of the question and am putting the correct code here for those who might be as dense as I am and not initially realize what is being said!
# New Outlook object
$ol = new-object -comobject "Outlook.Application";
# MAPI namespace
$mapi = $ol.getnamespace("mapi");
$folder = $mapi.Folders.Item('name#gmail.com').Folders.Item('Inbox')
# Get the items in the folder
$contents = $folder.Items
# Sort the items in the folder by the metadata, in this case ReceivedTime
$contents.Sort("ReceivedTime")
# Get the first item in the sorting; in this case, you will get the oldest item in your inbox.
$item = $contents.GetFirst()
echo $item
# If instead, you wanted to get the newest item, you could do the same thing but do $item = $contents.GetLast()
I have my current script display results but when it comes up it just puts everything in one long column but they are in order. Any ideas how I can get three columns called "computer name" "Patch#" "Installed?".
Below is my code
$strCategory = "computer"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$ObjSearcher.SearchRoot = $objDomain
$objSearcher.filter = ("(objectCategory=$strCategory)")
$colProplist = "name"
foreach($i in $colProplist){
$objSearcher.PropertiesToLoad.Add($i)
}
#Finds all operating systems and computer names
$colResults = $objSearcher.FindAll()
foreach($objResult in $colResults){
$objComputer = $objResult.Properties;
$names = $objComputer.name
# Define Hotfix to check
$CheckKBS = #("KB2937610", "KB4019264")
# Query the computers for the HotFix
foreach($name in $names){
foreach ($CheckKB in $CheckKBS) {
$HotFixQuery = Get-HotFix -ComputerName $name | Where-Object {$_.HotFixId -eq $CheckKB} | Select-Object -First 1;
if($HotFixQuery -eq $null) {
Out-file C:\Users\xxx\Desktop\12.csv -Append -InputObject "computername: $name"
Out-file C:\Users\xxx\Desktop\12.csv -Append -InputObject "“KB Patch: $CheckKB"
Out-file C:\Users\xxx\Desktop\12.csv -Append -InputObject "Hotfix $CheckKB is not installed on $name"
} else {
Out-file C:\xxx\xxx\xxx\12.csv -Append -InputObject "computername: $name"
Out-file C:\xxx\xxx\xxx\12.csv -Append -InputObject "KB Patch; $CheckKB"
Out-file C:\xxx\xxx\xxx\12.csv -Append -InputObject "Hotfix $CheckKB was installed on $name by $($HotFixQuery.InstalledBy)"
}
}
}
}
You're outputting raw text into a CSV which is OK but you have to maintain the intended destination format.
out-file is going to output raw text, one per line. That is why your calls are creating a single column. You can change your code to collapse your output to 1 line that is separated by commas:
Out-file C:\Users\xx\Desktop\12.csv -Append -InputObject “computer name: $name,KB Patch: $CheckKB,Hotfix $CheckKB is not installed on $name”
This will write 1 raw line to your CSV file. Your fields will be respected as columns assuming the rest of the CSV file is intact.
I doubt you want to repeat your headers per line but wanted to show you what it would look like. Now you are going to want to write the initial line of the CSV file before you start your loop to output the headers:
Out-file C:\Users\xx\Desktop\12.csv -Append -InputObject “Computer Name,KB Patch,Hotfix”
Then just remove the header stuff from the lines written inside your loop. This does mean that the header will be written with each run so you are either going to want to wipe the file at the start or you are going to want to test if it exists before you write the header.
There are better ways to do this but this doesn't require you to make significant changes to your work. I would create a custom object for each record, create a collection of those objects, and then export them via export-csv. If that would better suite your need please just comment a request for it.
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.
I recently finished my script with the help of someone on this site (Matt) Thanks again!
I now need to somehow get the logfile into a tabled format and I'm not sure how to implement that with the current setup of the script, any ideas?
Write-Host Report generated at (Get-date)
write-host("Lower Environments Status Check");
# Preprocessing Items
$msg = ""
$array = get-content C:\LowerEnvChecklist\appurls.txt
$log = "C:\LowerEnvChecklist\lowerenvironmentslog.txt"
$errorTexts = "error has occurred","Oops","Unable to display widget data","unexpected error occurred","temporarily unavailable","there was a problem"
$regex = ($errorTexts | ForEach-Object{[regex]::Escape($_)}) -join "|"
write-host("Checking appurls.txt...One moment please.");
("`n---------------------------------------------------------------------------") | out-file $log -Append
Get-Date | Out-File $log -Append
("`n***Checking Links***") | out-file $log -Append
("`n") | out-file $log -Append
# Loop through each element of the array.
ForEach($target in $array){
# Erase results for the next pass in case of error.
$result, $response, $stream, $page = $null
# Navigate to site urls
$result = [System.Net.WebRequest]::Create($target)
$response = $result.GetResponse()
$stream = [System.IO.StreamReader]$response.GetResponseStream()
$page = $stream.ReadToEnd()
# To ensure login/authentication pages that give a 403 response pages still show as online
If($response.StatusCode -eq 403){
$msg = " $target -----> is ONLINE!"}
# Determine if the status code 200 pages are truly up based on the information above.
If($response.StatusCode -eq 200){
# While the page might have rendered need to determine there are no errors present.
If($page -notmatch $regex){
$msg = " $target -----> is ONLINE!"
} else {
$msg = " $target -----> may be DOWN, please check!"
}
} else {
$msg = " $target -----> may be DOWN, please check!"
}
# Log Results.
$msg | Out-File $log -Append -width 120
write-host $msg
# Close the response.
$response.Close()
}
# Write completion to logfile.
("`n") | out-file $log -Append
("`n***Lower Environments Checklist Completed***") | out-file $log -Append
# Write completion to host.
write-host("Lower Environments Checklist Completed");
# Open logfile once script is complete.
Invoke-Item C:\LowerEnvChecklist\lowerenvironmentslog.txt
If you just want to view it in-script you could do Out-GridView on your log file. This will open a new window with a view of the data in the log file that looks like a table. Depending on your formatting you may have to add extra items like headers that are human readable.
To wet your whistle with structured output I opted to show you a CSV based solution. Either way all avenues require objects. What we do here is create a custom object that we populate as the script progresses. Each pass sends the details down the pipe. Using the pipeline we can use Export-CSV to collect all of the data in a nice file. Even filtering is possible now.
write-host("Lower Environments Status Check");
# Preprocessing Items
$array = Get-Content C:\LowerEnvChecklist\appurls.txt
$log = "C:\LowerEnvChecklist\lowerenvironmentslog.csv"
$errorTexts = "error has occurred","Oops","Unable to display widget data","unexpected error occurred","temporarily unavailable","there was a problem"
$regex = ($errorTexts | ForEach-Object{[regex]::Escape($_)}) -join "|"
# Loop through each element of the array. Use the pipeline to make output easier
$array | ForEach-Object{
# Keep the variable $target so it is not lost in scopes. Build the object to be completed as we go.
$target = [pscustomobject][ordered]#{
URL = $_
Status = ""
Detail = "N/A"
Timestamp = Get-Date
}
# Erase results for the next pass in case of error.
$result, $response, $stream, $page = $null
# Navigate to site urls. If we cant access the site set a flag to mark the site as down.
$result = [System.Net.WebRequest]::Create($target.URL)
$response = try{$result.GetResponse()}catch{$null}
switch([int]$response.StatusCode){
403{
$target.Status = "OK"
$target.Detail = "403"
}
200{
# Get page content to confirm up status
$stream = [System.IO.StreamReader]$response.GetResponseStream()
$page = $stream.ReadToEnd()
# While the page might have rendered need to determine there are no errors present.
If($page -notmatch $regex){
$target.Status = "OK"
} else {
$target.Status = "DOWN"
$target.Detail = "Pattern"
}
}
default{
$target.Status = "DOWN"
}
}
# Send the object down the pipeline
$target
# Close the response. The object might not exist so check before we call the methods.
if($response){$response.Close()}
if($stream){$stream.Close()}
} | Export-CSV -Path $log -NoTypeInformation
# Write completion to host.
write-host("Lower Environments Checklist Completed");
# Open logfile once script is complete.
Invoke-Item $log
I took the liberty off adding another column to your request called Detail it could add context. Not sure what you wanted from the date but if you have plenty of URLS and processing time then I suppose it could be of use. Also to reduce the if logic I added a switch statement. This would be more useful if you react to other status codes down the road. Still, good thing to know.
Sample Output
URL Status Detail Timestamp
--- ------ ------ ---------
https://7fweb DOWN N/A 1/11/2016 12:18:16 PM
http://www.google.ca OK N/A 1/11/2016 12:18:16 PM
http://www.microsoft.com DOWN Pattern 1/11/2016 12:18:16 PM
I added "windows" to $errorTexts to trigger a pattern match for microsoft.com
I'm an extremely novice Powershell student who was given the task of getting the following code to work and I keep getting an Empty Pipeline Error at the line remarked 'Gives Empty Pipeline Error'. After quite a few hours of researching this I am still stumped as to what is causing this. The script is supposed to search the Application.evtx log and return any errors from the last 24 hours. I would greatly appreciate any help that could get me pointed in the right direction.
Here's the code:
#look for Errors script
#Creates Function named CheckLogs
Function CheckLogs()
{
# Defines a named parameter $logfile as a string
param ([string]$logfile)
if(!$logfile) {write-host "Usage: ""C:\Windows\System32\winevt\Logs\Application.evtx"""; exit}
# Accesses the file stored in $logfile variable and looks for the string "ERROR"
cat $logfile | Select-string "ERROR" -SimpleMatch |select -expand line |
foreach
{
$_ -match '(.+)\s\[(ERROR)\]\S(.+)'| Out-Null
new-object psobject -Property#{Timestamp=[datetime]$matches[1];Error=$matches[2]}
| #Gives Empty Pipeline Error
where {$_.timestamp -gt (get-date).AddDays(-1)}
$error_time=[datetime]($matches[1])
if ($error_time -gt (Get-Date).AddDays(-1))
{
write-output "CRITICAL: There is an error in the log file $logfile around
$($error_time.ToShortTimeString( ))"; exit(2)
}
}
write-output "OK: There were no errors in the past 24 hours."
}
CheckLogs "C:\Windows\System32\winevt\Logs\Application.evtx" #Function Call
You can't put the pipe | character on a line by itself. You can end a line with | and then continue the pipeline on the next line though.
This should work:
new-object psobject -Property#{Timestamp=[datetime]$matches[1];Error=$matches[2]} |
where {$_.timestamp -gt (get-date).AddDays(-1)}