I have a log file Input.log which records failed and successful login attempts made by different users and it keeps updating in real time. I am interested only in failed login attempt made by one user i.e. master. Whenever there is a failed login attempt by user master, following 3 fixed text strings will always come in 3 consecutive lines as shown below in sample Input.log file:
Authenticating the user "master" with the password
Failed to logon to the system due to something unexpected
SSCMPE-00102: Failed to authenticate user. Invalid credentials. Enter valid credentials
Input.log file sample for a failed login attempt by master:
[2021-05-14T04:18:41.378-06:00] [FoundationServices0] [NOTIFICATION] [01216] [oracle.bi.bifndnepm.bpmui.logon.CSSAuthenticate] [tid: 30] [userId: <anonymous>] [ecid: 00j8DrPuyNGB1FwDwFj8CW0001hC0004FL,0:1] [APP: WORKSPACE#11.1.2.0] [SRC_CLASS: com.hyperion.bpm.logon.CSSAuthenticate] [SRC_METHOD: authenticateUser:473] Authenticating the user "master" with the password "*********".
[2021-05-14T04:18:41.573-06:00] [FoundationServices0] [ERROR] [02601] [oracle.bi.bifndnepm.bpmui.logon.LogonServlet] [tid: 30] [userId: <anonymous>] [ecid: 00j8DrPuyNGB1FwDwFj8CW0001hC0004FL,0:1] [APP: WORKSPACE#11.1.2.0] [SRC_CLASS: com.hyperion.bpm.logon.LogonServlet] [SRC_METHOD: writeLogonCssException:206] Failed to logon to the system due to something unexpected.[[
SSCMPE-00102: Failed to authenticate user. Invalid credentials. Enter valid credentials.
at com.hyperion.css.store.identity.IdentityStoreImpl.authenticate(IdentityStoreImpl.java:1845)
at com.hyperion.css.spi.impl.nvdb.NativeProvider.authenticate(NativeProvider.java:74)
at com.hyperion.css.facade.impl.CSSAbstractAuthenticator.authenticateUser(CSSAbstractAuthenticator.java:645)
at com.hyperion.css.facade.impl.CSSAPIAuthenticationImpl.authenticate(CSSAPIAuthenticationImpl.java:69)
I want to create a monitoring script sothat as soon as we have these 3 text strings appeared in 3 consecutive lines, I should get an email alert about the failed login attempt made by user master.
I will schedule the script to run in Windows task scheduler. I'd like to make the script run continuously to detect the failed login attempts in real time. So it should read only freshly written entries in Input.log file from the previous run of the script.
So far I have below code that, in failed.log, gives me all the lines matching above three strings coming consecutively in three lines (what I actually want) but also many other unwanted lines matching the three strings individually in different lines (which I don't want).
$File = "C:\data\Input.log"
$EmailParam=#{
To='usergroup#domain.com'
From='user#domain.com'
SmtpServer='smtp.serveraddress.com'
Subject='Failed Login Attempt by the user Master'
Body='Alert! Failed Login Attempt found for the user Master'
Attachment='failed.log'
}
$String='Authenticating the user "master" with the password','Failed to logon to the system due to something unexpected','SSCMPE-00102: Failed to authenticate user. Invalid credentials. Enter valid credentials'
Get-Content $File | Select-string -Pattern $String | Set-Content failed.log | ForEach {
Send-MailMessage #EmailParam
}
Would appreciate if you could guide me to fix it. Thanks!
Use Get-Content's -Wait switch to keep waiting - indefinitely - for new lines to be added to the log file.
Maintain state via a ForEach-Object call in which you can use -match, the regular-expression matching operator to examine the lines.
$matchingLineCount = 0
$matchingLines = #()
Get-Content -Wait $File | ForEach-Object {
if ($matchingLineCount -eq 2) { # 3rd line -> send email.
$matchingLines += $_
$matchingLines | Set-Content failed.log
Send-MailMessage #EmailParam
}
elseif ($matchingLineCount -eq 0 -and $_ -match 'Authenticating the user "master"') {
$matchingLines += $_
++$matchingLineCount
return
} elseif ($matchingLineCount -eq 1 -and $_ -match 'Failed to logon to the system due to something unexpected') {
$matchingLines += $_
++$matchingLineCount
return
}
$matchingLineCount = 0; $matchingLines = #()
}
Note:
Given that only 3 lines must be collected, I'm defining $matchingLines as a regular array (#()) and using += to "append" lines to it, for brevity and convenience.
However, in general this approach to iteratively building a collection is inefficient, because every += causes a new array to be created behind the scenes - of necessity, given that arrays are immutable with respect to their element count - see this answer for more information.
GitHub issue #5643 discusses switching PowerShell's default collection data structure from non-extensible arrays to an extensible collection type.
a possible solution: the regex pattern looks at 3 consecutives lines
$pat1 = 'Authenticating the user "master" with the password'
$pat2 = 'Failed to logon to the system due to something unexpected'
$pat2 = 'SSCMPE-00102: Failed to authenticate user. Invalid credentials. Enter valid credentials'
$pat = $pat1 + ".*?`r?`n.*?" + $pat2 + ".*?`r?`n.*?" + $pat3
$path = "C:\Users\ThierryK\Documents\test.log"
$lines = Get-Content -Raw -Path $path
$option = [System.Text.RegularExpressions.RegexOptions]::Singleline
$pattern = [regex]::new($pat, $option)
$matches = $pattern.Matches($lines)
foreach($m in $matches){
$m.Groups[0].Value
#send your email
}
i suggest you to keep the time each time you scan the log to avoid to scan the same thing every time
Related
I am writing a Powershell script to copy unencrypted EBS Snapshots in AWS to Encrypted Snapshots. In AWS the max number of concurrent copies is currently 20 at one time, but I have 1400 snapshots to copy. I wrote a script in Powershell using a For Each loop to loop through the snapshot IDs stored in a Text file, and it works as expected until it gets to 20 snapshots being copied. Then it will throw the following error and fail:
An error occurred (ResourceLimitExceeded) when calling the CopySnapshot operation: Too many snapshot copies in progress. The limit is 20 for this destination region.
I have tried to use a While Do statement, but I believe I am missing some items on here. The script is listed below. Essentially I am trying to have it if the script gets to 20 concurrent copies, it will retry on the one snapshot until a free spot opens up and then move on to the next. Ideally I would like to just have this run in the background for a day or so. See the current script below:
function Get-TimeStamp {
return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
}
$kmsID = "blah"
$region = "us-east-1"
$stoploop = $false
[int]$Retrycount = "0"
Foreach($line in get-content C:\snaps4.txt) {
do {
$desc = aws ec2 describe-snapshots --snapshot-ids $line | ConvertFrom-Json
$description = $desc.Snapshots.Description
Write-Output "$description"
$snap = aws ec2 copy-snapshot --description "[Copied $line from us-east-1] $description" --source-region $region --source-snapshot-id $line --encrypted --kms-key-id $kmsID | ConvertFrom-Json
$newsnap = $snap.SnapshotId
Write-Output "$(Get-TimeStamp) Created copy of $line $description with NEW SnapshotID $newsnap" >> C:\log.txt
$stoploop = $true
}
While ($Stoploop -eq $false)
}
Please let me know if you have any questions, and I appreciate any help in advance.
Thanks!
You can put the copy command inside a try/catch block.
Something like this:
try {
copy command;
mark as complete
}
catch{
mark as failed
}
one approach is to make the content file a csv with file name and complete columns. Use import-csv to read it; iterate the imported list when $_.complete -ne "Y" and set complete to "Y" when it succeeds. Export the file at the end.
Re-run as needed
I am trying to setup a log which would pull different information from another log file to log assets build by MDT using PowerShell. I can extract a line of log using simple get-content | select-string to get the lines i need so output looks like that
[LOG[Validate Domain Credentials [domain\user]]LOG]!
time="16:55:42.000+000" date="10-20-2017" component="Wizard"
context="" type="1" thread="" file="Wizard"
and I am curious if there is a way of capturing things like domain\user, time and date in a separate variables so it can be later passed with another data captured in a similar way in output file in a single line.
This is how you could do it:
$line = Get-Content "<your_log_path>" | Select-String "Validate Domain Credentials" | select -First 1
$regex = '\[(?<domain>[^\\[]+)\\(?<user>[^]]+)\].*time="(?<time>[^"]*)".*date="(?<date>[^"]*)".*component="(?<component>[^"]*)".*context="(?<context>[^"]*)".*type="(?<type>[^"]*)".*thread="(?<thread>[^"]*)".*file="(?<file>[^"]*)"'
if ($line -match $regex) {
$user = $Matches.user
$date = $Matches.date
$time = $Matches.time
# ... now do stuff with your variables ...
}
You might want to build in some error checking etc. (e.g. when no line is found or does not match etc.)
Also you could greatly simplify the regex if you only need those 3 values. I designed it so that all fields from the line are included.
Also, you could convert the values into more appropriate types, which (depending on what you want to do with them afterwards) might make handling them easier:
$type = [int]$Matches.type
$credential = New-Object System.Net.NetworkCredential($Matches.user, $null, $Matches.domain)
$datetime = [DateTime]::ParseExact(($Matches.date + $Matches.time), "MM-dd-yyyyHH:mm:ss.fff+000", [CultureInfo]::InvariantCulture)
My plan is to create a script that will gather each previous day's worth of failed logins from the security log. I want a way to count the number of times the same user triggers an alert since the end goal is to build a simple web site that displays the username, the number of failed logons they triggered and the name of the server where the alert was triggered. I have the start below:
#get a list of all disabled user email address and store it in
disabledUsers variable
$disabledUsers = Get-ADUser -filter * -SearchBase
"OU=Users,OU=Disabled,OU=COMPANY,DC=example,DC=local" -Properties mail | select -expandproperty mail
#empty array that will store all the usernames parsed below
$userArray = #()
#iterate through each user account in the disabledUsers variable
foreach ($user in $disabledUsers){
#for every email address, split the value based at the # symbol and retrieve the first field/index and add that to the userArray array
$userArray += $user.Split("#")[0]}
#sets the previous day's time and date
$yesterday = (Get-Date).AddDays(-1)
#get all failed login attempts since yesterday and extract the time of the error, server where error was logged, and user account that triggered the error
$failedLogins = get-eventlog -LogName Security -After $yesterday -EntryType FailureAudit -InstanceId 4625 | select timewritten,machinename,#{n='account';e={$_.replacementstrings[5]}}
#iterate through each entry in the failedLogins variable
foreach ($failedLogin in $failedLogins) {
<# define a counter variable to increment the check between the username in each failed event log against each
username defined in the userArray array. Loop keeps checking until $i is greater than or equal to the number of
entries in the userArray array
#>
for ($i=0; $i -lt $userArray.Length; $i++) {
#everytime $i is not equal or greater than # of counts in userArray, check and see if the
#username in the event log matches the current username in the userArray index (represented by the $i)
if ($failedLogins.account -match $userArray[$i]) {
#if there is a match......
} else {
}
}
}
I'm not sure how best to build this counter. In case anyone is wondering, the use case for this is because we started noticing a bunch of failed login attempts from a disabled user account about once every 1-2mins. Trying to build a way to monitor this.
I have a text file to process. Text file has some configuration data and some networking commands. I want to run all those network commands and redirect output in some log file.
At starting of text file,there are some configuration information like File-name and file location. This can be used for naming log file and location of log file. These line starts with some special characters like '<#:'. just to know that rest of the line is config data about file not the command to execute.
Now, before i want start executing networking commands (starts with some special characters like '<:'), first i want to read all configuration information about file i.e. file name, location, overwrite flag etc. Then i can run all commands and dump output into log file.
I used get-content iterator to loop over entire text file.
Question: Is there any way to start looping over file from a specific line again?
So that i can process config information first (loop till i first encounter command to execute, remember this line number), create log file and then keep running commands and redirect output to log file (loop from last remembered line number).
Config File looks like:
<#Result_File_Name:dump1.txt
<#Result_File_Location:C:\powershell
<:ping www.google.com
<:ipconfig
<:traceroute www.google.com
<:netsh interface ip show config
My powerhsell script looks like:
$content = Get-Content C:\powershell\config.txt
foreach ($line in $content)
{
if($line.StartsWith("<#Result_File_Name:")) #every time i am doing this, even for command line
{
$result_file_arr = $line.split(":")
$result_file_name = $result_file_arr[1]
Write-Host $result_file_name
}
#if($line.StartsWith("<#Result_File_Location:"))#every time i am doing this, even for command line
#{
# $result_file_arr = $line.split(":")
# $result_file_name = $result_file_arr[1]
#}
if( $conf_read_over =1)
{
break;
}
if ($line.StartsWith("<:")) #In this if block, i need to run all commands
{
$items = $line.split("<:")
#$items[0]
#invoke-expression $items[2] > $result_file_name
invoke-expression $items[2] > $result_file_name
}
}
If all the config information starts with <# just process those out first separately. Once that is done you can assume the rest are commands?
# Collect config lines and process
$config = $content | Where-Object{$_.StartsWith('<#')} | ForEach-Object{
$_.Trim("<#") -replace "\\","\\" -replace "^(.*?):(.*)" , '$1 = $2'
} | ConvertFrom-StringData
# Process all the lines that are command lines.
$content | Where-Object{!$_.StartsWith('<#') -and ![string]::IsNullOrEmpty($_)} | ForEach-Object{
Invoke-Expression $_.trimstart("<:")
}
I went a little over board with the config section. What I did was convert it into a hashtable. Now you will have your config options, as they were in file, accessible as an object.
$config
Name Value
---- -----
Result_File_Name dump1.txt
Result_File_Location C:\powershell
Small reconfiguration of your code, with some parts missing, would look like the following. You will most likely need to tweak this to your own needs.
# Collect config lines and process
$config = ($content | Where-Object{$_.StartsWith('<#')} | ForEach-Object{
$_.Trim("<#") -replace "\\","\\" -replace "^(.*?):(.*)" , '$1 = $2'
} | Out-String) | ConvertFrom-StringData
# Process all the lines that are command lines.
$content | Where-Object{!$_.StartsWith('<#') -and ![string]::IsNullOrEmpty($_)} | ForEach-Object{
Invoke-Expression $_.trimstart("<:") | Add-Content -Path $config.Result_File_Name
}
As per your comment you are still curious about your restart loop logic which was part of your original question. I will add this as a separate answer to that. I would still prefer my other approach.
# Use a flag to determine if we have already restarted. Assume False
$restarted = $false
$restartIndexPoint = 4
$restartIndex = 2
for($contentIndex = 0; $contentIndex -lt $content.Length; $contentIndex++){
Write-Host ("Line#{0} : {1}" -f $contentIndex, $content[$contentIndex])
# Check to see if we are on the $restartIndexPoint for the first time
if(!$restarted -and $contentIndex -eq $restartIndexPoint){
# Set the flag so this does not get repeated.
$restarted = $true
# Reset the index to repeat some steps over again.
$contentIndex = $restartIndex
}
}
Remember that array indexing is 0 based when you are setting your numbers. Line 20 is element 19 in the string array for example.
Inside the loop we run a check. If it passes we change the current index to something earlier. The write-host will just print the lines so you can see the "restart" portion. We need a flag to be set so that we are not running a infinite loop.
I have a script to set send on behalf of permissions in Exchange Management Shell, but when you try and use it it fails because the output of the first part is too long and truncates over 2 lines.
First thing we do is build our array from lists of people and put them into some variables to pass:
function Add-Send ($mailbox, $target) {
#"Granting send on behalf for $mailbox to $target"
Set-Mailbox -Identity $mailbox -GrantSendOnBehalfTo #{ Add = $target }
}
We pass a long list as the $target and the maibox name is $mailbox and if we output the text we get:
Set-Mailbox -Identity "mr.jeff" -GrantSendOnBehalfTo #{ Add = "alan.alanson", "bob.bobson", "steve.stevenson" }
All fine and good but if there are more than N characters in the output then we get a line break:
Set-Mailbox -Identity "mr.jeff" -GrantSendOnBehalfTo #{ Add = "alan.alanson", "bob.bobson", "steve.stevenson", ...
..., "cath.cathdotir" }
When you run this script with the overlength output, then command fails as the output which should be passed to the CLI is passed over more than one line. PowerShell treats each line as a separate command, and they obviously fail with bad syntax.
Our string is output from an array that we build like this:
function Send-Array ($mailbox) {
$target = Get-Content ".\list\dpt1.txt"
$target += Get-Content ".\list\$mailbox.txt"
$target += Get-Content ".\list\dpt2.txt"
$target = $target | Select-Object -Unique
$separator = '", "'
$target= $target -replace '^|$','"' -join ','
Add-Send $mailbox $target
}
This gives us an array with strings that look like:
"alan.alanson", "bob.bobson", "steve.stevenson"
From here I am at a loss any ideas would be much appreciated.
The obvious solution would be to pass the names one at a time, but due to a gotcha with Exchange Server every time you set send on behalf of permissions with PowerShell it wipes the existing permissions, so you only end up with he last person granted permissions being able to send on behalf of.
See this link for help with your underlying issue.
Very basically, you will have to:
get the DistinguishedName of the user you need to add
store the current value of GrantSendOnBehalfTo in a variable
append the new user's distinguished name to the list
replace GrantSendOnBehalfTo with the new list
Afterwards you should not need to pass endless strings to the EMS (I hope so).