Where "AND" not functioning as expected - powershell

I am pulling Windows event logs using Get-EventLog and removing log events I don't want to see using source and eventid as criteria. When I do parse using where, the values are note respected. For example if I do
$Events = Get-EventLog -ComputerName $computer -LogName Application
$events | ft source, eventid
I see the following:
Source EventID
------ -------
AutoEnrollment 34
If I do:
$events |
?{($_.Source -ne "AutoEnrollment" -and $_.EventID -ne 14)} |
ft source, eventid
The results are empty, which puzzles me because clearly the eventid does not match. I expect if I were evaluating against $_.eventid -ne 34, then the results would not show that event. This worked when I wrote the code on PowerShell 2 back in 2012. Now on v5.1 it fails to properly evaluate.
Has something changed that I should be aware of, or did I screw it up initially? If I AM doing this wrong, any suggestions on how to say "where event not match specified criteria as a set" so that source=autoenrollment and eventid=34 will show up, but events with source=autoenrollment and eventid=14 will not.

These two posts by mjsqu answered the question:
es, and #MathiasR.Jessen suggestion is right. NOT(A AND B) is not the same as NOT(A) AND NOT(B), it's equivalent to NOT(A) OR NOT(B). Why this used to work on PS2 is odd. – mjsqu 21 hours ago
I would rewrite as
... |? {-not ($_.Source -eq "AutoEnrollment" -and $_.EventID -eq 14)}
... which is arguably more human-readable.

Related

How to filter the output of Select-Object

I'm using this command to return a table of results:
Get-WinEvent -LogName 'System' -MaxEvents 40 | Select-Object TimeCreated, ID, ProviderName, LevelDisplayName, Message | Format-Table -AutoSize
Select-Object is specifically desired because it prevents the results being grouped by ProviderName. I want the results in a single table.
I want to filter results so that it's only returning the top 40 results where the ID is in a list... and I know I can use Where-Object to achieve this with ...Where-Object { $_.ID -match "41|1074|6006|6008" }..., but Where-Object returns the results grouped by ProviderName.
I'm pretty new to Powershell, I've done plenty of searching on the web and experimenting with piping the results of Select-Object, but can't get useful results.
How do I return the top X results matching a particular condition on a property such as ID, but also in a single table?
It's probably more efficient to prefilter by providing Get-WinEvent a filter directly rather than having it pull unfiltered events and then sifting for what you want.
The code below will return only what you want using such filter. Then you can use RetiredGeek's solution to produce a custom format-table view on the data :)
$xmlFilter = #'
<QueryList>
<Query Id="0" Path="System">
<Select Path="System">*[System[(EventID=41 or EventID=1074 or EventID=6006 or EventID=6008)]]</Select>
</Query>
</QueryList>
'#
Get-WinEvent -MaxEvents 40 -FilterXML $xmlFilter |
Select-Object TimeCreated, ID, ProviderName, LevelDisplayName, Message | Format-Table -AutoSize
An xml filter can be built directly from Windows Event Viewer using the filter gui and then clicking on the XML tab at the top. If you use it often you will eventually pick up on the syntax and be able to quickly write one yourself (or you can just keep using the filter option in Event Viewer =) )
P.S., there are -FilterXPath and -FilterHashtable options as well if you don't like the xml way (e.g. copy and pasting from Event Viewer like a crook... feels dirty)
Jlanger,
Here's one possible solution that you can adjust as necessary.
Note: this does require expanding the width of the Console Window to get reasonable readout. I set mine to a width of 150.
Clear-Host
$fmtGWE = #{Expression = {$_.TimeCreated};Label="Time Created";
Align="Right"},
#{Expression = {$_.Id};Label="ID";
;Align="Right"},
#{Expression = {$_.ProviderName};Label="Provider";
Align="Left"},
#{Expression = {$_.LevelDisplayName};Label="Level";
Align="Left"},
#{Expression = {$_.Message};Label="Message"}
Get-WinEvent -LogName 'System' -MaxEvents 40 |
Where-Object { $_.ID -match "7045|10016|30|50036" } |
Format-Table -Property $fmtGWE -AutoSize -Wrap
Sample Output using different event codes as I'm not on a domain.
If you can't save and/or the above as a script you can just keep it in a notepad file and copy & paste into your console.
You'll notice that even on my 27" screen you'll still have to scroll to read the entire message. You could also use Ctrl+Wheel to reduce text size.
Posting an answer with the final working version that I prefer, but leaving #Daniel's answer as the accepted answer since it works and was what led me to getting my final working version.
I found this question, which helped: Get-WinEvent -FilterHashTable with multiple IDs in a variable not working
I prefer this version as it's shorter and I think easier to read.
It also became apparent that returning fewer results was fine in my situation. It would be possible to filter by the date as well, but I don't have the time to put toward doing that.
Get-WinEvent -MaxEvents 10 -FilterHashtable #{logname='System'; ID = 41,1074,6006,6008} |
Select-Object TimeCreated, ID, LevelDisplayName, Message |
Format-Table -AutoSize
To reiterate for anyone looking at this answer, piping the results into Select-Object means that the results are in one table rather than being grouped by ProviderName.

powershell - show last event after filter with pipeline

I'm trying to get the last event (with a certain event ID and a certain word in the message field). The code I use:
Get-WinEvent #{logname = 'security';id=4663} | ? {$_.Message -like "*WriteData (or AddFile)*"}
My issue is that it finds multiple events and I only want to get the newest.
I know I can use -MaxEvents 1 (after sorting it by date) but I would need to place it after the pipeline (because I want the newest event after the Message filtering).
Any idea how can I do that?
Thanks in advance.
Found the answer to be a simple Select-Object.
I first forced it to be sorted by time with Sort-Object TimeCreated -Descending and then piped it to Select-Object -First 1.
The whole thing looks like this:
Get-WinEvent #{logname = 'security';id=4663} | ? {$_.Message -like "*WriteData (or AddFile)*"} | Sort-Object TimeCreated -Descending | Select-Object -First 1

Get-ADComputer Lastlogondate / Lastlogon

I'm currently asking myself if it is possible to determine the last logon time of any user of a computer object which is connected to an active directory?
I need to find out when any user was logged onto a specific computer which is still online, communicating with the domain but was not in use in the last X days by any user.
I've already tried the following queries:
get-adcomputer $computername -Properties lastlogon | select
#{Name="lastLogon";Expression={[datetime]::FromFileTime($_.'lastLogon')}}
AND
get-adcomputer za31testvmrobin -Properties lastlogondate
I'm expecting the timestamp of the last logondate of a user on a computer object.
Hope you can help me.
I somehow figured it out with help from #boxdog . Thanks for that.
Here is the Powershell Code in one line:
Get-EventLog -LogName Security -InstanceId 4624 -ComputerName $computer |
`where {$_.Message -match "Kontoname: USERNAME" -and
`$_.Message -match "Anmeldetyp: 2" } | select -First 1)
Kontoname = Accountname
Anmeldetyp = Logontype (2 means interactive from console with keyboard & mouse)
The tabulator is needed. You can also use wildcards like an asterisk.
I could not find an easier way to get it working. Therefor I had to use the comparison operator "match" to find a string with which I could search within the Message property of the Eventlog.
Unfortunately searching takes some time. Via remote it takes up to 5 minutes each computer which is quiet unsatisfying.
Maybe someone has another solution which is faster or knows a way to work parallel, actually I don't really know how to do that, because I'm getting content with
get-content c:\data\input.txt
Thanks in advance

Where-Object, Select-Object and ForEach-object - Differences and Usage

Where-Object, Select-Object and ForEach-Object
I am a PowerShell beginner. I don't understand too much. Can someone give examples to illustrate the differences and usage scenarios between them?
If you are at all familiar with either LINQ or SQL then it should be much easier to understand because it uses the same concepts for the same words with a slight tweak.
Where-Object
is used for filtering out objects from the pipeline and is similar to how SQL filters rows. Here, objects are compared against a condition, or optionally a ScriptBlock, to determine whether it should be passed on to the next cmdlet in the pipeline. To demonstrate:
# Approved Verbs
Get-Verb | Measure-Object # count of 98
Get-Verb | Where-Object Verb -Like G* | Measure-Object # 3
# Integers 1 to 100
1..100 | Measure-Object # count of 100
1..100 | Where-Object {$_ -LT 50} | Measure-Object # count of 49
This syntax is usually the most readable when not using a ScriptBlock, but is necessary if you want to refer to the object itself (not a property) or for more complicated boolean results. Note: many resources will recommend (as #Iftimie Tudor mentions) trying to filter sooner (more left) in the pipeline for performance benefits.
Select-Object
is used for filtering properties of an object and is similar to how SQL filters columns. Importantly, it transforms the pipeline object into a new PSCustomObject that only has the requested properties with the object's values copied. To demonstrate:
Get-Process
Get-Process | Select-Object Name,CPU
Note, though, that this is only the standard usage. Explore its parameter sets using Get-Help Select-Object where it has similar row-like filtering capabilities like only getting the first n objects from the pipeline (aka, Get-Process | Select-Object -First 3) that continue onto the next cmdlet.
ForEach-Object
is like your foreach loops in other languages, with its own important flavour. In fact, PowerShell also has a foreach loop of its own! These may be easily confused but are operationally quite different. The main visual difference is that the foreach loop cannot be used in a pipeline, but ForEach-Object can. The latter, ForEach-Object, is a cmdlet (foreach is not) and can be used for transforming the current pipeline or for running a segment of code against the pipeline. It is really the most flexible cmdlet there is.
The best way to think about it is that it is the body of a loop, where the current element, $_, is coming from the pipeline and any output is passed onto the next cmdlet. To demonstrate:
# Transform
Get-Verb | ForEach-Object {"$($_.Verb) comes from the group $($_.Group)"}
# Retrieve Property
Get-Verb | ForEach-Object Verb
# Call Method
Get-Verb | ForEach-Object GetType
# Run Code
1..100 | ForEach-Object {
$increment = $_ + 1
$multiplied = $increment * 3
Write-Output $multiplied
}
Edit (Feb, 2023): thanks to #IkemKrueger for a missing }.
You have two things in there: filtering and iterating through a collection.
Filtering:
principle: Always use filtering left as much as possible. These two commands do the same thing, but the second one won't transmit a huge chunk of data through the pipe (or network):
Get-Process | where-Object {$_.Name -like 'chrome'} | Export-Csv
'c:\temp\processes.csv'
Get-Process -Name chrome | Export-Csv c:\temp\processes.csv
This is great when working with huge lists of computers or big files.
Many commandlets have their own filtering capabilities. Run get Get-Help get-process -full to see what they offer before piping.
iterating through collections:
Here you have 3 possibilities:
batch cmdlets is commandlet built in capability of passing a collection to another commandlet:
Get-Service -name BITS,Spooler,W32Time | Set-Service -startuptype
Automatic
WMI methods - WMI uses it's own way of doing the first one (different syntax)
gwmi win32_networkadapterconfiguration -filter "description like
'%intel%'" | EnableDHCP()
enumerating objects - iterating through the list:
Get-WmiObject Win32_Service -filter "name = 'BITS'" | ForEach-Object
-process { $_.change($null,$null,$null,$null,$null,$null,$null,"P#ssw0rd") }
Credits:
I found explanations that cleared the mess in my head around all these things in a book called : Learn Powershell in a month of lunches (chapters 9 and 13 in this case)

Which is the most comprehensive way to check applied KBs to a Windows machine?

I am using Powershell to run a status of a list of KBs and see if they are applied or not.
I have found a few ways and I have seen inconsistencies with the numbers they are reporting. Which is right?
You can check SYSTEMINFO and get a list of hotfixes. You can also use the Get-Hotfix cmdlet, which is an alias for gwmi Win32_QuickFixEngineering or you can use wmic qfe list (WMI-CLI QuickFixEngineering List).
So, why am I getting different numbers when I do a quick count?
i.e. (Get-HotFix).Count and (wmic qfe list).Count
Using those two returns 153 and 310, respectively.
What gives? Why does it return different values? Are all of the applied KBs not listed in the Get-Hotfix cmdlet?
Before anyone asks, yes, I have restarted the machine and I haven't applied any since it was restarted and updated. That is Day 1 stuff...
WMIC has obscure blank lines which might be muddying the waters a bit. Here's simple, not very good, parser for wmic qfe (Windows 10 so who knows if it'll transpose).
The hope is that you can compare the lists.
$qfe = wmic qfe list brief | Select-Object -Skip 1 | Where-Object { $_.Trim().Length -gt 0 } | ForEach-Object {
[PSCustomObject]#{
Description = $_.Substring(0, 17).Trim()
HotFixId = $_.Substring(30, 10).Trim()
}
}
Compare-Object (Get-HotFix) $qfe -Property HotFixID -IncludeEqual
So is that enough? No, not really. QFE is great but indicative only. I'm trying to remember the circumstance that invalidates it. I'll come back to this.