Extract Username From Log Text using Powershell - powershell

I'm trying to extract all usernames that has failed login atempts from Event Viewer log and then list only the usernames. However the data for each entry is text so I have a hard time extracting only the names (Intruder123 in this case). It would be a couple of hundred account names stored in an array.
$String = Get-WinEvent #{LogName='Security';ProviderName='Microsoft-Windows-Security-Auditing';ID=4625 } -ComputerName SECRETSERVER |
Select-Object -ExpandProperty Message
$string -match "Account Name: (?<content>.*)"
$matches['content']
The data looks like this (multiple times):
Account For Which Logon Failed:
Security ID: S-1-0-0
Account Name: Intruder123
Account Domain: SECRET.LOCAL

I think you could collect some more information like the time the failed logon happened and on which computer. For that, create a resulting array of objects.
Also, trying to parse the Message property can be cumbersome and I think it is much better to get the info from the Event as XML:
$filter = #{LogName='Security';ProviderName='Microsoft-Windows-Security-Auditing';ID=4625 }
$result = Get-WinEvent -FilterHashtable $filter -ComputerName SECRETSERVER | ForEach-Object {
# convert the event to XML and grab the Event node
$eventXml = ([xml]$_.ToXml()).Event
$userName = ($eventXml.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
$computer = ($eventXml.EventData.Data | Where-Object { $_.Name -eq 'WorkstationName' }).'#text'
# output the properties you need
[PSCustomObject]#{
Time = [DateTime]$eventXml.System.TimeCreated.SystemTime
UserName = $userName
Computer = $computer
}
}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'X:\FailedLogons.csv' -NoTypeInformation

Related

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

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.

Powershell add null content and not null content to one CSV

I need to include 'Installed' and 'Not Installed' data in one CSV
I think I need to incorporate an -OR logical operator to include TRUE/FALSE
output in one CSV. Idk how to do that yet.
There's a folder with many *ServerData files that contain a list of KBs with
possible duplicate KBs.
There is a *ServerData file for each server, with possible duplicate files.
I want to test whether any of them contain KB2151757 and KB4556403.
Then output the results to a csv with a status of either Installed or Not
Installed.
Currently it only returns a list of computers with the KB installed.
If the $patch is not found, it currently returns nothing (null).
For each $computer searched, it needs to return the specified fields for the
[PSCustomObject]
I'm thinking that maybe I just need to take a function to find 'installed' and a function to find 'not installed' and add the results together to export. Idk how to do that. I feel like there must be an easier way.
Click to view a sample of the CSV
$computers = Get-Item -path F:\*ServerData | Select-Object -ExpandProperty basename
$patch = gc -path F:\*ServerData | Sort-Object -Unique | Select-String KB2151757, KB4556403 #'KB(\d+)'
$output = ForEach ($computer in $computers) {
ForEach ($kb in $patch) {
if ($null -eq $patch){
[PSCustomObject]#{
Status = 'Not Installed'
Server = $computer
KB = $kb
}
} else{
[PSCustomObject]#{
Status = 'Installed'
Server = $computer
KB = $kb
}
}
}
}
$output | Export-csv C:\KB-Report.txt -notypeinformation -delimiter ',' -encoding utf8
If you start by grouping the files by the associated computer name, then the procedure becomes straightforward (pseudocode):
for each Computer
for each ExpectedPatch
if ServerData for Computer contains ExpectedPatch
Output object with 'Installed' status for ExpectedPatch on Computer
else
Output object with 'NotInstalled' status for ExpectedPatch on Computer
So let's give that a try:
# Define the articles we're looking for
$ExpectedPatches = 'KB2151757', 'KB4556403'
# Enumerate and group data files by computer name, output as hashtable
# The resulting hashtable will have the computer name is Name and the associated files as its value
$ServerDataPerComputer = Get-Item -Path F:\*ServerData |Group BaseName -AsHashtable
foreach($Computer in $ServerDataPerComputer.GetEnumerator())
{
foreach($Patch in $ExpectedPatches)
{
# Pipe all the file references to Select-String, look for the KB ID, return after the first match if any
$Status = if($Computer.Value |Select-String "\b$Patch\b" |Select-Object -First 1){
'Installed'
}
else {
# Select-String didn't find the KB ID in any of the files
'NotInstalled'
}
[pscustomobject]#{
Status = $Status
Server = $Computer.Name
KB = $Patch
}
}
}

Create a PowerShell script that would get the last 30 days history logon of Domain Admin member

I would like to write a Power Shell script that would do the following:
- If the user is member of (Domain admins) get me the last 30 days history logon of this user in any Domain joined computer.
I created something now but it still lacks a lot as it reads the security events on the Domain controller and brings the users,time and matches them with the Domain admin group as in the attached screenshot
I would appreciate if someone can help me evolve this script into something useful
$Rusers = Get-WinEvent -Computer dc02 -FilterHashtable #{Logname='Security';ID=4672} -MaxEvents 50 |
` select #{N='User';E={$_.Properties[1].Value}},TimeCreated
$DAUsers = Get-ADGroupMember -Identity "Domain Admins"
Foreach ($DAUser in $DAUsers){
$DomainUser = $DAUser.SamAccountName
foreach ($Ruser in $Rusers){
$RAUser = $Ruser.User
If ($RAUser -match $DomainUser){
Write-Host $Ruser is domain admin }
}[![enter image description here][1]][1]
}
# Get domain admin user list
$DomainAdminList = Get-ADGroupMember -Identity 'Domain Admins'
# Get all Domain Controller names
$DomainControllers = Get-ADDomainController -Filter * | Sort-Object HostName
# EventID
$EventID = '4672'
#
# Get only last 24hrs
$Date = (Get-Date).AddDays(-1)
# Limit log event search for testing as this will take a LONG time on most domains
# For normal running, this will have to be set to zero
$MaxEvent = 50
# Loop through Dcs
$DALogEvents = $DomainControllers | ForEach-Object {
$CurDC = $_.HostName
Write-Host "`nSearching $CurDC logs..."
Get-WinEvent -Computer $CurDC -FilterHashtable #{Logname='Security';ID=$EventID;StartTime = $Date} -MaxEvents $MaxEvent |`
Where-Object { $_.Properties[1].Value -in $DomainAdminList.SamAccountName } |`
ForEach-Object {
[pscustomobject]#{SamAccountName = $_.Properties[1].Value;Time = $_.TimeCreated;LogonEventLocation = $CurDC}
}
}
All the Domain Admin logon events should now be in $DALogEvents
You'll need to group results by name, then export to a file
Thanks a lot for your help, I apologize I was not clear enough. The kind of information I am looking for is pertaining to users who have been utilized for services e.g. (SQL reporting Services, Or Sccm Service ..etc )
This script does what I want but it doesn't run only for domain admin users, it runs for everyone basically and not sure if there's a limit to the time/date.
Is it possible to adjust it to let it run against Domain Admin users for 30 days and print information like. Source IP, User, Target Dc, Date?
Get-EventLog -LogName Security -InstanceId 4624 |
ForEach-Object {
# translate the raw data into a new object
[PSCustomObject]#{
Time = $_.TimeGenerated
User = "{0}\{1}" -f $_.ReplacementStrings[5], $_.ReplacementStrings[6]
Type = $_.ReplacementStrings[10]
"Source Network Address" = $_.ReplacementStrings[18]
Target = $_.ReplacementStrings[19]
}
}
I've added couple more of custom objects to get the result that I needed. I think turning this into a function would be great tool to use for auditing.
Thanks a lot to you #Specialist
# Get domain admin user list
$DomainAdminList = Get-ADGroupMember -Identity 'Domain Admins'
# Get all Domain Controller names
$DomainControllers = Get-ADDomainController -Filter * | Sort-Object HostName
# EventID
$EventID = '4624'
#
# Get only last 24hrs
$Date = (Get-Date).AddDays(-3)
# Limit log event search for testing as this will take a LONG time on most domains
# For normal running, this will have to be set to zero
$MaxEvent = 100
# Loop through Dcs
$DALogEvents = $DomainControllers | ForEach-Object {
$CurDC = $_.HostName
Write-Host "`nSearching $CurDC logs..."
Get-WinEvent -ComputerName $CurDC -FilterHashtable #{Logname='Security';ID=$EventID;StartTime = $Date} -MaxEvents $MaxEvent |`
Where-Object { $_.Properties[5].Value -in $DomainAdminList.SamAccountName } |`
ForEach-Object {
[pscustomobject]#{SourceIP = $_.Properties[18].Value; SamAccountName = $_.Properties[5].Value;Time = $_.TimeCreated;LogonEventLocation = $CurDC}
}
}
$DALogEvents

How do you export a list of PC names and a specific group membership of that endpoint using powershell?

I have a list of end points and we are trying to see if they have this specific group membership but I cannot figure out how to export the endpoint and the group its a member of.
$Groups = foreach ($pc in (Get-Content "C:\Users\*\Desktop\DefualtTest.csv")) {
try {
Get-ADComputer $pc -Properties memberof |
select -Expand memberof |
dsget group -samid |
? {$_ -match 'bit9'}
} catch {
Write-Output "$pc does not have bit9 group"
}
}
$Groups | Out-File "C:\Users\*\Desktop\testONE.csv"
trying to do this in comments is ... to difficult. [grin]
here's an example of what i mean by "do it one line at a time" in the foreach loop ...
# fake reading a list of systems from a text file
# in real life, use Get-Content
$ComputerList = #'
pc01
pc02
pc03
pc04
pc05
pc666
'# -split [environment]::NewLine
# fake getting a list of Computers & the groups they are in
# in real life, use Get-ADComputer & the ".MemberOf" property
$GroupMembershipLookup = #{
'pc01' = #('GroupA', 'GroupB')
'pc02' = #('GroupB', 'GroupC', 'GroupD')
'pc03' = #('GroupA', 'GroupB', 'GroupE')
'pc04' = #('GroupZ')
}
$NoGroupFound = '__No Group Found__'
$Results = foreach ($CL_Item in $ComputerList)
{
# use a real call to Get-ADComputer here [*grin*]
# pro'ly something like "(Get-ADComputer $CL_Item -Property MemberOf).MemberOf -join '; '"
$GroupList = $GroupMembershipLookup[$CL_Item] -join '; '
if ([string]::IsNullOrEmpty($GroupList))
{
$GroupList = $NoGroupFound
}
[PSCustomObject]#{
ComputerName = $CL_Item
GroupMembership = $GroupList
}
}
# on screen
$Results
# to CSV
$Results |
Export-Csv -LiteralPath "$env:TEMP\Del_GroupMembershipList.csv" -NoTypeInformation
on screen output ...
ComputerName GroupMembership
------------ ---------------
pc01 GroupA; GroupB
pc02 GroupB; GroupC; GroupD
pc03 GroupA; GroupB; GroupE
pc04 GroupZ
pc05 __No Group Found__
pc666 __No Group Found__
csv content ...
"ComputerName","GroupMembership"
"pc01","GroupA; GroupB"
"pc02","GroupB; GroupC; GroupD"
"pc03","GroupA; GroupB; GroupE"
"pc04","GroupZ"
"pc05","__No Group Found__"
"pc666","__No Group Found__"
what the above does ...
creates an array of system names as if they had been read in via Get-Content
creates a lookup table as if one had used Get-ADComputer to get the group membership for each system
made a "not found" value & stored that in a $Var for reuse
iterated thru the $ComputerList collection
grabbed the list/array of groups and converted them into a semicolon delimited string
tested for "no groups found" & added the value saved earlier if the list was empty
built a [PSCustoObject] and sent that to the output stream
captured the output stream to an array named $Results all at one time to avoid the += array penalty
displayed the results on screen
sent the $Results collection to a CSV file

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