Get-WinEvent TimeCreated printing out empty - powershell

I'm trying to test a powershell script that finds a specific event viewer task that excludes a certain case. For some reason, it's printing the event created time as empty. I think this is why it's falling into a wrong case. Why is this created time empty? This is an example for this website, so Init variable name doesn't quite make sense below, with chromoting.
#Look for crash within 150 hours of boot, and with Init within 7 minutes before that
$today=[system.datetime](Get-Date)
$startTime=$today.AddHours(-150)
$events = Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='SlotBroker';StartTime=$($startTime);EndTime=$($today);} -ErrorAction SilentlyContinue
if($events -ne $null)
{
foreach ($event in $events)
{
$crashOccurredTime=$event.TimeCreated
$lookForInitStart = $event.TimeCreated.AddMinutes(-7)
$eventInits = {Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='chromoting';StartTime=$lookForInitStart;EndTime=$crashOccurredTime;} -ErrorAction SilentlyContinue
}
if($eventInits -ne $null)
{
foreach ($eventInit in $eventInits)
{
#check that didn't have Terminate in that timeframe because we don't want this case
#look for exclude case of Terminate between Init and crash
$initTime = $eventInit.TimeCreated #chromoting
Write-Host "initTime $($initTime)" ##this is blank time
$eventInitTerminate = {Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='AppMgr';StartTime=$initTime;EndTime=$crashOccurredTime;} -ErrorAction SilentlyContinue | Where-Object {(-PipelineVariable Message -Match 'Terminate function called') -or (-PipelineVariable Message -Match 'Terminate function returned')}
}
if($eventInitTerminate -ne $null)
{ #it always falls in here no matter if it should or not.
Write-Host "Found application.exe after Init with Terminate TimeCreatedCrash $($event.TimeCreated) ProviderName $($event.ProviderName) Message $($event.Message) TerminateTime $($eventInitTerminate.TimeCreated)"
}
else #this will print
{
Write-Host "Found application.exe after Init without Terminate TimeCreated $($event.TimeCreated) ProviderName $($event.ProviderName) Message $($event.Message) InitTime $($eventInit.TimeCreated)"
}
} #foreach
}
}
Looking at the event log, I see this:
Error 8/11/2022 9:43 SlotBroker
Information 8/11/2022 9:37 chromoting
Information 8/11/2022 936 AlarmSoundHelper
This is a test case and should be falling into #this will print, but it prints the above case. I think it's because of the time printing blank, so it finds the Terminate elsewhere in the event log. Why is that time not printing out right? I need the time to see if I need to notify me of the event log or not.
The purpose of this script is to avoid events with Terminate between SlotBroker and chromoting. As you can see, it's not in this case, but falls into that if statement like it found events. We don't have PowerShellISE on this computer with the eventLog, so I can't step through.
When I was working on the script in PowerShellISE on my laptop, it seemed like $eventInit might not know what TimeCreated is, but it's not causing an error. I'm not sure how to get that TimeCreated.
Update:
I added this below the $lookForInitStart and it prints ok
Write-Host "lookForInitStart $($lookForInitStart)"
prints
lookForInitStart 8/11/2022 09:36
I'm unsure why the initTime is printing blank.
I got the $initTime to not be empty with this line and used that for all TimeCreated to make sure they were correct.
$initTime = $eventInit | Select-Object -Expand TimeCreated #chromoting
But it's still falling into the $eventInitTerminate block, even though it doesn't have the Terminate events in there.
That would be helpful if someone else knows why it's falling into the "Found application.exe after Init with Terminate..." printout, and I would accept that answer.

I got the $initTime to not be empty with this line and used that for all TimeCreated to make sure they were correct.
$initTime = $eventInit | Select-Object -Expand TimeCreated #chromoting
For the Terminate block, I changed this to fix it (simplified the match for terminate):
$today=[system.datetime](Get-Date)
$startTime=$today.AddHours(-135)
write-host "startTime $($startTime)"
$events = Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='SlotBroker';StartTime=$($startTime);EndTime=$($today);} -ErrorAction SilentlyContinue
if($events -ne $null)
{
foreach ($event in $events)
{
$crashOccurredTime=$event | Select-Object -Expand TimeCreated
write-host "crashOccurredTime $($crashOccurredTime)"
$lookForInitStart = $event.TimeCreated.AddMinutes(-7)
Write-Host "lookForInitStart $($lookForInitStart)"
$eventInits = Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='chromoting';StartTime=$lookForInitStart;EndTime=$crashOccurredTime;} -ErrorAction SilentlyContinue
if($eventInits -ne $null)
{
foreach ($eventInit in $eventInits)
{
#check that didn't have Terminate in that timeframe because we don't want this case
#look for exclude case of Terminate between Init and crash
$initTime = $eventInit | Select-Object -Expand TimeCreated #chromoting
Write-Host "initTime $($initTime)"
$eventInitTerminate = Get-WinEvent -FilterHashtable #{LogName='Application';ProviderName='AppMgr';StartTime=$initTime;EndTime=$crashOccurredTime;} -ErrorAction SilentlyContinue | Where-Object -PipelineVariable Message -Match 'Terminate'
if($eventInitTerminate -ne $null)
{
Write-Host "*****Found application.exe after Init with Terminate TimeCreatedCrash $($event.TimeCreated) ProviderName $($event.ProviderName) Message $($event.Message) TerminateTime $($eventInitTerminate.TimeCreated)*****"
}
else #this will print
{
Write-Host "***Found application.exe after Init without Terminate TimeCreated $($event.TimeCreated) ProviderName $($event.ProviderName) Message $($event.Message) InitTime $($eventInit.TimeCreated)***"
}
} #foreach
}
else
{
Write-Host "No application Crash found after Init in 4 hours preceding shell command"
}
}
}#if
else
{
Write-Host "no events found that meet criteria of crash after init"
}

Related

Powershell extract error from a text file on different computers

I am trying to extract a list of computer having an error in a specific text file. I did the following :
$computernames = Get-Content C:\PC.txt
foreach ($server in $computernames)
{
$filepath = Test-Path "\\$server\c$\"
if ($filepath -eq "True") {
Get-Content "\\$server\c$\file.log" | Select-String "Error" -quiet | Select-Object $server
}
}
That is returning me the first computer with an error, then only some blank lines in the command window.
If I try to get it in a file with out-file or extract or whatever, I have a blank file.
Thanks in advance for any clue on the matter.
Regards.
There are multiple things you can do with this. Let me tell you first what you are looking for:
So lets say you have an error , in order to capture that you should use try/catch.
Replace :
Get-Content "\\$server\c$\file.log" | Select-String "Error" -quiet | Select-Object $server
With This:
try
{
Get-Content "\\$server\c$\file.log" | Select-String "Error" -quiet | Select-Object $server
}
catch
{
$Error >> ErrorFile.txt
}
Second thing, I would suggest is. Lets say you have 10 computers and the lets assume the 8th one is having some error. But still you do not wish to stop the loop and proceed with the remaining computers of the loop. In that case you should use 2 try/catch like this:
$computernames = Get-Content C:\PC.txt
try
{
foreach ($server in $computernames)
{
$filepath = Test-Path "\\$server\c$\"
try{
if ($filepath -eq "True") {
try
{
Get-Content "\\$server\c$\file.log" | Select-String "Error" -quiet | Select-Object $server
}
catch
{
$Error >> ErrorFile.txt
}
}
}
catch
{
$_.Exception.Message >> Errorfile.txt
}
}
}
catch
{
$_.Exception.Message >> Errorfile.txt
}
So the approach is, if there is any error inside the foreach then the internal catch block will catch it and the error message will be parsed to the errorfile.txt keeping the loop running and will proceed with the remaining computers.
If there is some issue with the main $computernames while getting the content of the file, then it will come to the main catch block with the respective error message parsed and appended in the errorfile.txt
Hope it helps you in understanding the effectiveness of try/catch

Why Am I geting an Empty Pipeline Error in this Script

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)}

Powershell get-process query

I want to write a simple If Statement which checks if an Process Exists.
If it Exists, something should start.
Like this, but working.. ;)
If ((Get-Process -Name Tvnserver.exe ) -eq $True)
{
Stop-Process tnvserver
Stop-Service tvnserver
Uninstall...
Install another Piece of Software
}
Else
{
do nothing
}
Thanks
Get-Process doesn't return a boolean value and the process name is listed without extension, that's why your code doesn't work. Drop the extension and either check if the result is $null as Musaab Al-Okaidi suggested, or cast the result to a boolean value:
if ( [bool](Get-Process Tvnserver -EA SilentlyContinue) ) {
# do some
} else {
# do other
}
If you don't want the script to do anything in case the process isn't running: just omit the else branch.
This will evaluate to true if the process doesn't exist:
(Get-Process -name Tvnserver.exe -ErrorAction SilentlyContinue) -eq $null
or if you want to change it you can negate the statement as follows:
-not ( $(Get-Process -name Tvnserver.exe -ErrorAction SilentlyContinue) -eq $null )
It's important to have have -ErrorAction SilentlyContinue to avoid any errors been thrown if a process doesn't exist.

Powershell Issues with .contains function for strings

OK, i am trying to get this script to work and not continuously hit the same computers over and over and am having trouble getting it working. I don't think this is the best way to do it and if you have any suggestions please and thank you. Anyways there seems to be an issue with line 6 "IF (!$Succssful.Contains($Computer)) {" it doesn't shoot an error at me but instead ends the script per-maturealy. I have tried removing the "!" but no luck as I expected.
$Computers = "TrinityTechCorp"
$HotFixes = Get-Content HotFixes.csv
While ($Successful -AND $Successful.count -ne $Computers.count) {
ForEach ($Computer in $Computers) {
IF (!$Succssful.Contains($Computer)) {
If (Test-Connection $Computer) {
$Comparison = get-hotfix -ComputerName $Computer | Select -expand HotFixID
ForEach ($HotFix in $HotFixes) {
IF ($Comparison -NotLike "*$HotFix*") {
Write-Host "$Computer missing $HotFix"
}
$Successful += $Computer
}
}
}
}
}
This line
While ($Successful -AND $Successful.count -ne $Computers.count) {
can never evaluate to $true. The .count-property of a non-existing variable ($Successful) is $null, and the same goes for a string (because you specify only 1 computer, it's not an array).
To force Powershell to return the "true" number of items, use #($Successful).count and #($Computers).count instead. This will give you 1, even if $Computers wasn't defined as an array. So, with variables substituted, your line is interpreted as
While ($null -AND $null.count -ne $null) {
In Powershell 3, as you can read here, this is no longer the case.

Run N parallel jobs in powershell

I have the following powershell script
$list = invoke-sqlcmd 'exec getOneMillionRows' -Server...
$list | % {
GetData $_ > $_.txt
ZipTheFile $_.txt $_.txt.zip
...
}
How to run the script block ({ GetDatta $_ > $_.txt ....}) in parallel with limited maximum number of job, e.g. at most 8 files can be generated at one time?
Same idea as user "Start-Automating" posted, but corrected the bug about forgetting to start the jobs that are held back when hitting the else clause in his example:
$servers = #('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n')
foreach ($server in $servers) {
$running = #(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -ge 4) {
$running | Wait-Job -Any | Out-Null
}
Write-Host "Starting job for $server"
Start-Job {
# do something with $using:server. Just sleeping for this example.
Start-Sleep 5
return "result from $using:server"
} | Out-Null
}
# Wait for all jobs to complete and results ready to be received
Wait-Job * | Out-Null
# Process the results
foreach($job in Get-Job)
{
$result = Receive-Job $job
Write-Host $result
}
Remove-Job -State Completed
The Start-Job cmdlet allows you to run code in the background. To do what you'd ask, something like the code below should work.
foreach ($server in $servers) {
$running = #(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le 8) {
Start-Job {
Add-PSSnapin SQL
$list = invoke-sqlcmd 'exec getOneMillionRows' -Server...
...
}
} else {
$running | Wait-Job
}
Get-Job | Receive-Job
}
Hope this helps.
It should be really easy with the Split-Pipeline cmdlet of the SplitPipeline module.
The code will look as simple as this:
Import-Module SplitPipeline
$list = invoke-sqlcmd 'exec getOneMillionRows' -Server...
$list | Split-Pipeline -Count 8 {process{
GetData $_ > $_.txt
ZipTheFile $_.txt $_.txt.zip
...
}}
Old thread but I think this could help:
$List = C:\List.txt
$Jobs = 8
Foreach ($PC in Get-Content $List)
{
Do
{
$Job = (Get-Job -State Running | measure).count
} Until ($Job -le $Jobs)
Start-Job -Name $PC -ScriptBlock { "Your command here $Using:PC" }
Get-Job -State Completed | Remove-Job
}
Wait-Job -State Running
Get-Job -State Completed | Remove-Job
Get-Job
The "Do" loop pause the "foreach" when the amount of job "running" exceed the amount of "$jobs" that is allowed to run.
Than wait for the remaining to complete and show failed jobs...
Background jobs is the answer. You can also throttle the jobs in the run queue using [System.Collection.Queue]. There is a blog post from PowerShell team on this topic: https://devblogs.microsoft.com/powershell/scaling-and-queuing-powershell-background-jobs/
Using queuing method is probably the best answer to throttling background jobs.
I use and improove a multithread Function, you can use it like :
$Script = {
param($Computername)
get-process -Computername $Computername
}
#('Srv1','Srv2') | Run-Parallel -ScriptBlock $Script
include this code in your script
function Run-Parallel {
<#
.Synopsis
This is a quick and open-ended script multi-threader searcher
http://www.get-blog.com/?p=189#comment-28834
Improove by Alban LOPEZ 2016
.Description
This script will allow any general, external script to be multithreaded by providing a single
argument to that script and opening it in a seperate thread. It works as a filter in the
pipeline, or as a standalone script. It will read the argument either from the pipeline
or from a filename provided. It will send the results of the child script down the pipeline,
so it is best to use a script that returns some sort of object.
.PARAMETER ScriptBlock
This is where you provide the PowerShell ScriptBlock that you want to multithread.
.PARAMETER ItemObj
The ItemObj represents the arguments that are provided to the child script. This is an open ended
argument and can take a single object from the pipeline, an array, a collection, or a file name. The
multithreading script does it's best to find out which you have provided and handle it as such.
If you would like to provide a file, then the file is read with one object on each line and will
be provided as is to the script you are running as a string. If this is not desired, then use an array.
.PARAMETER InputParam
This allows you to specify the parameter for which your input objects are to be evaluated. As an example,
if you were to provide a computer name to the Get-Process cmdlet as just an argument, it would attempt to
find all processes where the name was the provided computername and fail. You need to specify that the
parameter that you are providing is the "ComputerName".
.PARAMETER AddParam
This allows you to specify additional parameters to the running command. For instance, if you are trying
to find the status of the "BITS" service on all servers in your list, you will need to specify the "Name"
parameter. This command takes a hash pair formatted as follows:
#{"key" = "Value"}
#{"key1" = "Value"; "key2" = 321; "key3" = 1..9}
.PARAMETER AddSwitch
This allows you to add additional switches to the command you are running. For instance, you may want
to include "RequiredServices" to the "Get-Service" cmdlet. This parameter will take a single string, or
an aray of strings as follows:
"RequiredServices"
#("RequiredServices", "DependentServices")
.PARAMETER MaxThreads
This is the maximum number of threads to run at any given time. If ressources are too congested try lowering
this number. The default value is 20.
.PARAMETER SleepTimer_ms
This is the time between cycles of the child process detection cycle. The default value is 200ms. If CPU
utilization is high then you can consider increasing this delay. If the child script takes a long time to
run, then you might increase this value to around 1000 (or 1 second in the detection cycle).
.PARAMETER TimeOutGlobal
this is the TimeOut in second for listen the last thread, after this timeOut All thread are closed, only each other are returned
.PARAMETER TimeOutThread
this is the TimeOut in second for each thread, the thread are aborted at this time
.PARAMETER PSModules
List of PSModule name to include for use in ScriptBlock
.PARAMETER PSSapins
List of PSSapin name to include for use in ScriptBlock
.EXAMPLE
1..20 | Run-Parallel -ScriptBlock {param($i) Start-Sleep $i; "> $i sec <"} -TimeOutGlobal 15 -TimeOutThread 5
.EXAMPLE
Both of these will execute the scriptBlock and provide each of the server names in AllServers.txt
while providing the results to GridView. The results will be the output of the child script.
gc AllServers.txt | Run-Parallel $ScriptBlock_GetTSUsers -MaxThreads $findOut_AD.ActiveDirectory.Servers.count -PSModules 'PSTerminalServices' | out-gridview
#>
Param(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
$ItemObj,
[ScriptBlock]$ScriptBlock = $null,
$InputParam = $Null,
[HashTable] $AddParam = #{},
[Array] $AddSwitch = #(),
$MaxThreads = 20,
$SleepTimer_ms = 100,
$TimeOutGlobal = 300,
$TimeOutThread = 100,
[string[]]$PSSapins = $null,
[string[]]$PSModules = $null,
$Modedebug = $true
)
Begin{
$ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
ForEach ($Snapin in $PSSapins){
[void]$ISS.ImportPSSnapIn($Snapin, [ref]$null)
}
ForEach ($Module in $PSModules){
[void]$ISS.ImportPSModule($Module)
}
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
$RunspacePool.CleanupInterval=1000
$RunspacePool.Open()
$Jobs = #()
}
Process{
#ForEach ($Object in $ItemObj){
if ($ItemObj){
Write-Host $ItemObj -ForegroundColor Yellow
$PowershellThread = [powershell]::Create().AddScript($ScriptBlock)
If ($InputParam -ne $Null){
$PowershellThread.AddParameter($InputParam, $ItemObj.ToString()) | out-null
}Else{
$PowershellThread.AddArgument($ItemObj.ToString()) | out-null
}
ForEach($Key in $AddParam.Keys){
$PowershellThread.AddParameter($Key, $AddParam.$key) | out-null
}
ForEach($Switch in $AddSwitch){
$PowershellThread.AddParameter($Switch) | out-null
}
$PowershellThread.RunspacePool = $RunspacePool
$Handle = $PowershellThread.BeginInvoke()
$Job = [pscustomobject][ordered]#{
Handle = $Handle
Thread = $PowershellThread
object = $ItemObj.ToString()
Started = Get-Date
}
$Jobs += $Job
}
#}
}
End{
$GlobalStartTime = Get-Date
$continue = $true
While (#($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0 -and $continue) {
ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){
$out = $Job.Thread.EndInvoke($Job.Handle)
$out # return vers la sortie srandard
#Write-Host $out -ForegroundColor green
$Job.Thread.Dispose() | Out-Null
$Job.Thread = $Null
$Job.Handle = $Null
}
foreach ($InProgress in $($Jobs | Where-Object {$_.Handle})) {
if ($TimeOutGlobal -and (($(Get-Date) - $GlobalStartTime).totalseconds -gt $TimeOutGlobal)){
$Continue = $false
#Write-Host $InProgress -ForegroundColor magenta
}
if (!$Continue -or ($TimeOutThread -and (($(Get-Date) - $InProgress.Started).totalseconds -gt $TimeOutThread))) {
$InProgress.thread.Stop() | Out-Null
$InProgress.thread.Dispose() | Out-Null
$InProgress.Thread = $Null
$InProgress.Handle = $Null
#Write-Host $InProgress -ForegroundColor red
}
}
Start-Sleep -Milliseconds $SleepTimer_ms
}
$RunspacePool.Close() | Out-Null
$RunspacePool.Dispose() | Out-Null
}
}
Old thread, but my contribution to it, is the part where you count the running jobs. Some of the answers above do not work for 0 or 1 running job. A little trick I use is to throw the results in a forced array, and then count it:
[array]$JobCount = Get-job -state Running
$JobCount.Count