(PowerShell) How do I filter usernames with Get-EventLog - powershell

I'm working on a Powershell script to get all users who have logged in/out of a server in the past 7 days, where their name is not like "*-organization". The below works, but no matter what I try I'm not able to filter names
$logs = get-eventlog system -ComputerName $env:computername -source Microsoft-Windows-Winlogon -After (Get-Date).AddDays(-7)
$res = #()
ForEach ($log in $logs)
{
if($log.instanceid -eq 7001){
$type = "Logon"
}
Elseif ($log.instanceid -eq 7002){
$type = "Logoff"
}
Else { Continue }
$res += New-Object PSObject -Property #{Time = $log.TimeWritten; "Event" = $type; User = (New-Object System.Security.Principal.SecurityIdentifier $Log.ReplacementStrings[1]).Translate([System.Security.Principal.NTAccount])}};
$res
I've tried adding this line in various places and ways, but no matter what I can't get it to filter. It either fails and tells me my operator must have a property and value, or it runs fine and ignores any username filtering.
| Where-Object $_.User -notlike "*-organization"
Is it even possible to filter the login username with this method? If so, what am I doing wrong? If it's not possible, is there another way I can get what I need?

There would have to be a property named 'user' for that to work. Get-eventlog is actually obsolete now, and replaced by get-winevent. Unfortunately, you have to get into the xml to filter by usersid. I've included a time filter.
$a = get-winevent #{logname='system';
providername='Microsoft-Windows-Winlogon'} -MaxEvents 1
$e = $a.ToXml() -as 'xml'
$e.event.EventData
Data
----
{TSId, UserSid}
get-winevent #{logname='system';providername='Microsoft-Windows-Winlogon';
data='S-2-6-31-1528843147-473324174-2919417754-2001';starttime=(Get-Date).AddDays(-7);
id=7001,7002}
In powershell 7 you can refer to the eventdata named data fields directly:
get-winevent #{logname='system';providername='Microsoft-Windows-Winlogon';
usersid='S-2-6-31-1528843147-473324174-2919417754-2001'}
The get-winevent docs say you can use "userid" in the filterhashtable, but I can't get that to work.
EDIT: Actually this works. But without limiting it too much, at least for me.
get-winevent #{logname='system';userid='js2010'}
get-winevent #{providername='Microsoft-Windows-Winlogon';userid='js2010'}

You can do this with the -FilterXPath parameter like below:
$filter = "(*[System/EventID=7001] or *[System/EventID=7002]) and *[System/Provider[#Name='Microsoft-Windows-Winlogon']]"
$result = Get-WinEvent -LogName System -FilterXPath $filter | ForEach-Object {
# convert the event to XML and grab the Event node
$eventXml = ([xml]$_.ToXml()).Event
$eventData = $eventXml.EventData.Data
$userSID = ($eventData | Where-Object { $_.Name -eq 'UserSid' }).'#text'
$userName = [System.Security.Principal.SecurityIdentifier]::new($userSID).Translate([System.Security.Principal.NTAccount])
# you can add username filtering here if you like.
# remember the $userName is in formal DOMAIN\LOGONNAME
# if ($username -notlike "*-organization") {
# output the properties you need
[PSCustomObject]#{
Time = [DateTime]$eventXml.System.TimeCreated.SystemTime
Event = if ($eventXml.System.EventID -eq 7001) { 'LogOn' } else { 'LogOff' }
UserName = $userName
UserSID = $userSID
Computer = $eventXml.System.Computer
}
# }
}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'X:\TheOutputFile.csv' -NoTypeInformation
Note, I have commented out the username filtering in the code. It is just there to give you an idea of where to put it. Of course, you can also filter the $result afterwards:
$result | Where-Object { $_.UserName -notlike "*-organization" }

Adding to #js2010's helpful answer, and with the assumption you're using PowerShell 5.1. I usually identify the property array index and use Select-Object to create a custom property as needed.
$WinEvents =
get-winevent #{logname='system'; providername='Microsoft-Windows-Winlogon'} |
Select-Object #{Name = 'Time'; Expression = {$_.TimeCreated}},
#{Name = 'Event'; Expression = { If($_.ID -eq 7001){'Logon'} ElseIf($_.ID -eq 7002){ 'Logoff' } } },
#{Name = 'User'; Expression = { [System.Security.Principal.SecurityIdentifier]::new( $_.Properties[1].Value ).Translate([System.Security.Principal.NTAccount]) } }
In your case this should add a property called User with a value like DomainName\UserName to the objects. I also added expressions to derive the other properties you were adding to your custom objects. Select-Object emits custom objects as well so this should give the result you're looking for.
Let me know if this helps.
Update
Respectfully, the other 2 answers make the assumption that you are looking for logon/off events for a specific user. That's not how I read the question; in particular:
"get all users who have logged in/out of a server"
While PowerShell 7+ does let you directly cite UserID in the FilterHashtable, it's not very useful here because we're not seeking events for a specific user. Furthermore, it seems unhelpful for the ultimate output as by default it echoes as a SID. It would still need to be translated, not only for display but for further filtering. I'm also not positive that UserID will always be the same as Properties[1], there's certainly some variance when looking at other event IDs.
The XML work is very cool, but I don't think it's called for here.
There were some issues with my answer as well. I overlooked filtering the event IDs & dates up front. I also realized we don't need to instantiate [System.Security.Principal.SecurityIdentifier] class because the property is already typed as such. Along with some readability improvements I corrected those issues below.
# Should be the 1st line!
using NameSpace System.Security.Principal
$ResolveEventType = #{ 7001 = 'Logon'; 7002 = 'Logoff' }
$FilterHashTable =
#{
LogName = 'system'
ProviderName = 'Microsoft-Windows-Winlogon'
ID = 7001,7002
StartTime = (Get-Date).AddDays(-7)
}
[Array]$WinEvents =
Get-WinEvent -FilterHashtable $FilterHashTable |
Select-Object #{ Name = 'Time'; Expression = { $_.TimeCreated } },
#{ Name = 'Event'; Expression = { $ResolveEventType[ $_.ID ] } },
#{ Name = 'User'; Expression = { $_.Properties[1].Value.Translate( [NTAccount] ) } }
$WinEvents |
Where-Object{ $_.UserName -notlike "*-organization" } |
Format-Table -AutoSize
This tested good in PowerShell 5.1 & 7.0. I added Format-Table to display the output, but you can just change that out for an Export-Csv command as needed
Note: The last 2 pipelines can be combined, but I thought this was a
little more readable.
Let me know if this helps.

Related

PowerShell variable definition from a pscustomobject

i've got this piece of code from a script i found on the web (just showing the part that interests me)
ForEach ($Computer in $Computername) {
$adsi = [ADSI]"WinNT://$Computername"
$adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
[pscustomobject]#{
UserName = $_.Name[0]
SID = ConvertTo-SID -BinarySID $_.ObjectSID[0]
PasswordAge = [math]::Round($_.PasswordAge[0]/86400)
LastLogin = If ($_.LastLogin[0] -is [datetime]){$_.LastLogin[0]}Else{'Never logged on'}
UserFlags = Convert-UserFlag -UserFlag $_.UserFlags[0]
MinPasswordLength = $_.MinPasswordLength[0]
MinPasswordAge = [math]::Round($_.MinPasswordAge[0]/86400)
MaxPasswordAge = [math]::Round($_.MaxPasswordAge[0]/86400)
BadPasswordAttempts = $_.BadPasswordAttempts[0]
MaxBadPasswords = $_.MaxBadPasswordsAllowed[0]
}
}
}
the code displays things on the console, but i would like to define/use these values as variables instead (as i want to use them in a hash table afterwards to send them in a http/POST request afterwards)
is there a way to get all these attributes as variables such as $LastLogin, $MinPasswordAge etc ?
as i don't want to display them, but send them in a POST like this :
$postParams = #{LastLogin=$LastLogin;MinPasswordAge=$MinPasswordAge}
Invoke-WebRequest -Uri http://example.com/foobar -Method POST -Body $postParams
to be honest i'm a complete newbie in PowerShell (i'm a Perl guru) and i don't know what pscustomobject does in there, i just want to define the variables in that loop, and use them at the end.
i've tried a couple of things with no success (can post them if required)
thanks !
Your own solution works, but only if you perform all processing inside the ForEach-Object script block (unless there's only ever 1 iteration, which doesn't appear to be the case here).
If you want to process the results later, you can simply collect them in an array by assigning the entire foreach loop to a variable (code shortened):
$allUsers = foreach ($Computer in $Computername) {
$adsi = [ADSI]"WinNT://$Computername"
$adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
# Output a custom object for each user.
[pscustomobject]#{
ComputerName = $Computer # also record the computer name
UserName = $_.Name[0]
SID = ConvertTo-SID -BinarySID $_.ObjectSID[0]
# ...
}
}
}
You can then simply enumerate the collected [pscustomobject]s and access their properties rather than using variables:
foreach ($user in $allUsers) {
# Use the properties to define a hashtable for later use in a http/POST request.
$ht = #{
User = $user.UserName
# ...
}
}
nm,
i found the solution a minute ago.
just got rid of that pscustomobject hash completely, and assigning the variables directory
$adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
$UserName = $_.Name[0]
$SID = ConvertTo-SID -BinarySID $_.ObjectSID[0]
$PasswordAge = [math]::Round($_.PasswordAge[0]/86400)
$LastLogin = If ($_.LastLogin[0] -is [datetime]){$_.LastLogin[0]}Else{'Never logged on'}
$UserFlags = Convert-UserFlag -UserFlag $_.UserFlags[0]
$MinPasswordLength = $_.MinPasswordLength[0]
$MinPasswordAge = [math]::Round($_.MinPasswordAge[0]/86400)
$MaxPasswordAge = [math]::Round($_.MaxPasswordAge[0]/86400)
$BadPasswordAttempts = $_.BadPasswordAttempts[0]
$MaxBadPasswords = $_.MaxBadPasswordsAllowed[0]
Write-Host $UserName
}
}

Matching SID from AD and Event Viewer

I'm trying to make a script that searches AD for locked accounts, as well as parses the Security log in Event Viewer and then compare the SID's, and if they match, display information of the user that has the SID.
Import-Module ActiveDirectory
$PDC = "DOMAINCONTROLLER"
$UserInfo = Search-ADAccount -LockedOut
$LockedOutEvents = Get-WinEvent -ComputerName $PDC -FilterHashtable
#{LogName='Security';Id=4740} | Sort-Object -Property * -Descending
Foreach($Event in $LockedOutEvents){
If($Event.Properties[2] -Match $UserInfo.SID.value)
{
$Event | Select-Object -Property #(
#{Label = 'User'; Expression = {$_.Properties[0].Value}}
#{Label = 'DomainController'; Expression = {$_.MachineName}}
#{Label = 'EventId'; Expression = {$_.Id}}
#{Label = 'LockoutTimeStamp'; Expression = {$_.TimeCreated}}
#{Label = 'Message'; Expression = {$_.Message -split "`r" | Select -First 1}}
#{Label = 'LockoutSource'; Expression = {$_.Properties[1].Value}}
)
}}
There seems to be an issue with the arguments in the If statement If($Event.Properties[2] -Match $UserInfo.TargetSID)
The output of $Event.Properties[2] is like this:
Value
-----
S-1-1-1-111111111-111111111-111111111-22222
The output of $UserInfo.SID.Value:
S-1-1-1-111111111-111111111-111111111-11111
S-1-1-1-111111111-111111111-111111111-11111
S-1-1-1-111111111-111111111-111111111-22222
S-1-1-1-111111111-111111111-111111111-11111
S-1-1-1-111111111-111111111-111111111-11111
As you can see one SID is found in both outputs but when matching these two i get "False" as a response. Does anyone have any idea why this happens?
Thank you for your time.
It looks like you're comparing a SecurityIdentifier object to an array of strings (at least that output looks like it's an array - you can use $UserInfo.SID.value.GetType() to make sure). There are two issues with your current code:
The -Match operator only works with two strings, so you can't use that here. But you can use Contains() on the array.
You need to convert the SecurityIdentifier to a string. The Value property does that.
Try this:
If ($UserInfo.SID.value.Contains($Event.Properties[2].Value))

get-winevent: cannot filter specific field FailureReason %%2313

I'm poking around with get-winevent for the event 4625 because i need the failure-reason, which is in a xml field of the event.
the first thing i tried is to print out all fields hoping, the failure reason is available somewhere in the data-path of the xml, with no success.
i did not found a way to query the statuscode %%2313 to something with powershell to get the readable text.
the last thing i did is filtering the specific event and write the statusmessage myself in a custom field of my export file.
I used this code to accomplish that:
Get-WinEvent -FilterHashtable #{Path="c:\temp\test.evtx";} -ErrorAction SilentlyContinue |
Where-Object {($_.id -eq "4625" -and $_.properties[9].value -in "%% 2313")}|ForEach-Object{
$SelectorStrings = [string[]]#(
'Event/EventData/Data[#Name="TargetUserName"]',
'Event/EventData/Data[#Name="TargetDomainName"]',
'Event/EventData/Data[#Name="WorkstationName"]',
'Event/EventData/Data[#Name="IpAddress"]',
'Event/EventData/Data[#Name="IpPort"]',
'Event/EventData/Data[#Name="LogonType"]',
'Event/EventData/Data[#Name="FailureReason"]',
'Event/EventData/Data[#Name="Remark"]'
)
$PropertySelector = [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new($SelectorStrings)
$TargetUserName,$TargetDomainName,$WorkstationName,$IpAddress,$IpPort,$LogonType,$FailureReason,$Remark = $_.GetPropertyValues($PropertySelector)
$Remark ="Unbekannter Benutzername oder ungültiges Kennwort."
#Create the PSCustomObject from the given Fieldnames
[PSCustomObject]#{
TimeCreated = $_.TimeCreated
Id = $_.Id
UserName = $TargetUserName
Domain = $TargetDomainName
WorkstationName = $WorkstationName
IPAddress = $IpAddress
Port = $IpPort
LogonType = $LogonType
Message = ($_.Message).split(".")[0]
Remark = $Remark
FailureReason =$FailureReason
}
}|Export-Csv -NoTypeInformation -Force -Encoding UTF8 -Path 'c:\temp\failurereason.csv'
but: it seams i cannot filter for %%2313 as property value. no idea why. Even with quotation marks or something, dont get any event out.

How to get server information from VMware

I have access to the VMWare GUI and I can easily export all the columns such as UPtime, IPAddress, Notes, DNS, GuestOs, State, Name and so on.
I want to right a script that can automatically get this information daily. So gar I was only able to get the server name, power state and VMhost. for some reason VMware is making it so hard to extract that information. I used the script below and I thought by adding the columns I mentioned above to this script, I should be able to retireve the data I need. But it doesn't work that way. Can someone please tell me how I can get this information?
Thanks,
Add-PSSnapin vmware.vimautomation.core
Connect-VIServer SERVERNAME
Get-VM|Select Name, VMHost, Id, PowerState
Exit 0
After digging into the system and many hours of research I found the solution. I just wish VMWare would make it easier to retrieve data or at least improve the manual.
The following code creates two files; one with the server information and another one with Uptime information.
Get-VM | select name,VMHost, #{ Name = "IP Addresses"; Expression = { $_.Guest.IPAddress }}, #{ Name = "PowerState"; Expression = { $_.Guest.State }} , #{ Name = "GuestOS"; Expression = { $_.Guest }}, Notes | Export-Csv -Path "HQstat.csv"
Get-Stat -Entity * -Stat sys.uptime.latest -Realtime -MaxSamples 1| Export-Csv -Path "HQtime.csv"
Why not use the views? They have all the information that you need. Code below assumes you are connected to the vCenter.
$vmView = Get-View -ViewType VirtualMachine -Property Name,Config,Guest,Runtime
$hostView = Get-View -ViewType HostSystem -Property Name
$date = Get-Date
Foreach ($vm in $vmView)
{
If ($vm.Runtime.BootTime -ne $null)
{
$dateDiff = $date.Subtract($vmView.Runtime.BootTime)
}
Else
{
$dateDiff = $null
}
foreach ($h in $hostView)
{
If ($vm.Runtime.Host -eq $h.MoRef)
{
$tempHost = $($h.Name)
Break
}
}
$global:Export += #([PSCustomObject]#{
VMName = $($vm.Name)
ID = $($vm.Config.Uuid) #or use $($vm.MoRef)
Host = $tempHost
PowerState = $($vm.Guest.GuestState)
IPAddress = $($vm.Guest.IPAddress)
Notes = $($vm.Config.Annotations)
UptimeMinutes = $($dateDiff.TotalMinutes)
})
$dateDiff = $null
$tempHost = $null
}
$exportFileName = "C:\temp\VMInformation.csv"
$Export | Export-Csv $exportFileName -Force -NoTypeInformation

Powershell passing variable as a filter string

I'm trying to write a script to retrieve the username of logged in users from servers, (This is an exercise in working with unformatted string data I'm aware of other methods to get this data so please don't suggest them)
I'm trying to pass a numeric string from a WMI query into a where-object filter
This is reading the WMI output from the Win32_LoggedOnUser class
$Name | where {$_.Dependent -like $ID } | select Antecedent
the problem seems to be in reading the ID variable, I've tried several variations which is a value concatenated with some wildcards for the filter format, I was tipped off to this when I converted the string to CSV format so I could more easily do a [regex]::Split, I get an error that the InputObject is null, If I run the above line alone I just get back a null result, if I manually enter the filter string I get the output I want.
[String]$ID = "'*"+$UserSessions.LogonId+"*'"
If I do a write-host I just get back '146771' which is what I seem to want,and get-member shows it to be a [System.String]
then I'm throwing this to a split that grabs the last token which is the username the whole script works fine if I manually enter the filter string just not with the variable in any format I've tried
${ID} ($ID) ""$ID"" $($ID)
Here's the full script for reference
$UserSessions = GWMI Win32_LogonSession | where { $_.LogonType -eq 2}
[String]$ID = "'*"+$UserSessions.LogonId+"*'"
$Name = GWMI Win32_LoggedOnUser
$Results = $Name | where {$_.Dependent -like $ID } | select Antecedent
$Split = $Results | ConvertTo-Csv
$Splat = [regex]::Split($Split, """")[9]
Write-Host "User = $Splat"
gwmi Win32_LogonSession may produce more than one result, so you need to account for that. Also, splitting produces less fields than you expect.
This worked for me:
gwmi Win32_LogonSession | ? { $_.LogonType -eq 2 } | % {
$ID = "*$($_.LogonId)*"
gwmi Win32_LoggedOnUser | ? { $_.Dependent -like $ID } | select Antecedent
} | % {
$name = ($_.Antecedent -split '"')[3]
Write-Host "User = $name"
}