How can I get the total number of Event ID error 4625 and for each and every Windows Server in my AD domain using the below Powershell script?
$DCServers = Get-ADDomainController -filter * | select -ExpandProperty hostname
$events = #()
$totalCt = 0
$servers = #()
Foreach ($Server in $DCServers)
{
Write-Host "Calling Get-WinEvent for $Server"
$serverEvents = Get-WinEvent -ComputerName $Server -FilterHashtable #{ logname = 'Security'; id = 4625 } -EA 0
if (!$?)
{
Write-Host "Get-WinEVent failure for $Server"
continue
}
if ($null -ne $serverEvents)
{
$totalCt += $serverEvents.Count
$servers += [PsCustomObject] #{ $server = $serverEvents.Count }
Write-Host $server $serverEvents.Count
}
$serverEvents | ForEach-Object {
$events += [PsCustomObject] #{
Date = $_.TimeCreated
"Event Id" = $_.Id
"User Name" = $_.Properties[6].Value + "\" + $_.Properties[5].Value ## fixed
"IPAddress" = $_.Properties[21].Value
"FailureReason" = (($_.message -split "\n") | Select-String -Pattern "Failure Reason:\s+(.+)").matches[0].groups[1].value
"Status Code" = $_.message -split '\s{4}' | Select-String -Pattern "Status"
"Logon Type" = $_.Properties[10].Value
"DC Logged On" = $_.Properties[13].value ## this is "workstation that processed the request", not the DC Logged On
}
}
}
$HTML = '<h1>Head</h1>'
$GetDate = Get-Date
$Report = 'C:\clu\temp-4625.html'
#convert the array of events to HTML
$Events |
Select-Object Date, "Event Id", "User Name", "FailureReason", "Status Code", "DC Logged On", "Logon Type" |
Convertto-html -head $HTML -PreContent "<H2>Accounts that Failed to Log On</H2>", "<H2>$GetDate </H2>" -PostContent "<p></p>Total 4625 records: $totalCt <p></p>" |
Out-File $Report -append
Write-Host "Total 4625 records: $totalCt"
Write-Host "4625 records per server:"
$servers | ft -auto
Write-Host "4625 records grouped by user"
$events | group "User Name" | sort Count
The goal is to see which servers have Event 4625 and group it by the content to see which IP or AD account failed logins where possible?
Before digging into how to extract the workstation IP address and how to group the events by specific properties, let me suggest rewriting your existing code slightly, given your goal.
Doing $event = New-Object psobject |Select listOfPropertyNames and then assigning the values to each property separately is going to be slow - something I imagine you might want to avoid if you have many servers to query.
Since PowerShell 3.0, we can instantiate a new custom object in one go with the following syntax:
$newObject = [pscustomobject]#{
PropertyName = "Value"
}
So, refactoring your existing code, we end up with something like:
$events += [pscustomobject]#{
Date = $_.TimeCreated
"Event Id" = $_.Id
"User Name" = $_.Properties[5].Value + "\" + $_.Properties[6].Value
"FailureReason" = (($_.Message -split "\n") | Select-String -Pattern "Failure Reason:\s+(.+)").Matches[0].Groups[1].Value
"Status Code" = $_.Message -split '\s{4}' | Select-String -Pattern "Status"
"Logon Type" = $_logontype[ [int] $_.Properties[10].Value ]
"DC Logged On" = $_.Properties[13].Value
}
Another change we might wanna make is change the data type of $events - when you instantiate an empty array with #(), PowerShell will allow you to add new items to the array with +=, but there's a catch - arrays are of a fixed size, so if you keep adding new items via +=, PowerShell will have to stop and resize the array by creating a larger underlying array and then copying the existing array items into the new, larger array. This obviously takes some time and might incur unnecessary memory acquisition as well.
To work around this, use a list instead - lists are designed with dynamic sizing in mind, so will perform better even when you add a 1000s of items to it:
$events = New-Object 'System.Collections.Generic.List[psobject]'
Now, back to the question - how to group all the events by IP address - first of all, we need to extract the workstation IP address in order to me able to group on it later, so let's add an extra property to the custom object we created:
$events += [pscustomobject]#{
# ...
IPAddress = $_.Properties[21].Value
}
Now that the IPAddress is present, grouping the objects based on it is as simple as:
$events | Group-Object IPAddress
Note that you can also get these fields from the xml of the event:
$a = Get-WinEvent -Max 1 #{logname='Security'; id=4625}
$xml = [xml]$a.ToXml()
$xml.event.EventData.data
Name #text
---- -----
SubjectUserSid S-1-5-18
SubjectUserName COMP$
SubjectDomainName DOM
SubjectLogonId 0x3e7
TargetUserSid S-1-0-0
TargetUserName admin
TargetDomainName COMP
Status 0xc000006d
FailureReason %%2313
SubStatus 0xc000006a
LogonType 7
LogonProcessName User32
AuthenticationPackageName Negotiate
WorkstationName COMP
TransmittedServices -
LmPackageName -
KeyLength 0
ProcessId 0xa60
ProcessName C:\Windows\System32\svchost.exe
IpAddress 127.0.0.1
IpPort 0
Compare with .Properties
$a.properties
Value
-----
S-1-5-18
COMP$
DOM
999
S-1-0-0
admin
COMP
-1073741715
%%2313
-1073741718
7
User32
Negotiate
COMP
-
-
0
2656
C:\Windows\System32\svchost.exe
127.0.0.1
0
Related
I'm Importing a CSV file and reading a column that look like this
Exchange Mailboxes
Include:[john.doe#outlook.com]
Include:[david.smith#outlook.com]
Include:[kevin.love#outlook.com]
I use Get-EXOMailbox to get their DisplayName and Id. After that I'm trying to pass it in my New-Object like below so that I can export it. The problem I have is when I look at my Excel file, it showing System.Object[] on every row instead of showing each actual DisplayName and Id.
Any help on how to display it correctly would be really appreciated.
$result = Import-Csv "C:\AuditLogSearch\Dis\Modified-Audit-Log-Records.csv" |
Where-Object { -join $_.psobject.Properties.Value } |
ForEach-Object {
$exoMailbox = ($_.'Exchange Mailboxes' -split '[][]')[1]
$exoUser = Get-EXOMailbox -Filter "PrimarySmtpAddress -eq '$exoMailbox'"
# Construct and output a custom object with the properties of interest.
[pscustomobject] #{
UserName = $exoUser.DisplayName
UserId = $exoUser.Identity
}
}
New-Object PsObject -Property #{
'Searched User' = $result.UserName //I'm trying to pass here
'SharePoint URL' = $spUrl
'Searched User GMID' = $result.UserId //and here
'Site Owner' = $spositeOwner
User = $u.User
"Result Status" = $u."Result Status"
"Date & Time" = $u."Date & Time"
"Search Conditions" = $u."Search Conditions"
"SharePoint Sites" = $u."SharePoint Sites"
"Exchange Public Folders" = $u."Exchange Public Folders"
"Exchange Mailboxes" = $u."Exchange Mailboxes".Split([char[]]#('[', ']'))[1]
"Case Name" = $u."Case Name"
"Search Criteria" = $u."Search Criteria"
"Record Type" = $u."Record Type"
"Hold Name" = $u."Hold Name".Split(('\'))[1]
"Activity" = if ($null -ne ($importData | where-object { $_.Name -eq $u."Activity" }).Value) { ($importData | where-object { $_.Name -eq $u."Activity" }).Value }
else { $u."Activity" }
} | Select-object -Property User, "Date & Time", "Case Name", "Hold Name", "Record Type", "Activity" , "Searched User", "Searched User GMID", "SharePoint URL", "Exchange Mailboxes", "Exchange Public Folders" , "Search Criteria", "Result Status"
}
$xlsx = $result | Export-Excel #params
$ws = $xlsx.Workbook.Worksheets[$params.Worksheetname]
$ws.Dimension.Columns
$ws.Column(1).Width = 20
$ws.Column(2).Width = 20
$ws.Column(3).Width = 15
$ws.Column(4).Width = 15
$ws.Column(5).Width = 15
$ws.Column(6).Width = 160
$ws.View.ShowGridLines = $false
Close-ExcelPackage $xlsx
$result is an array of objects, containing an object for each non-empty row in your input CSV; thus, adding values such as $result.UserName to the properties of the object you're creating with New-Object will be arrays too, which explains your symptom (it seems that Export-Excel, like Export-Csv doesn't meaningfully support array-valued properties and simply uses their type name, System.Object[] during export).
It sounds like the easiest solution is to add the additional properties directly in the ForEach-Object call, to the individual objects being constructed and output via the existing [pscustomobject] literal ([pscustomobject] #{ ... }):
$result =
Import-Csv "C:\AuditLogSearch\Dis\Modified-Audit-Log-Records.csv" |
Where-Object { -join $_.psobject.Properties.Value } | # only non-empty rows
ForEach-Object {
$exoMailbox = ($_.'Exchange Mailboxes' -split '[][]')[1]
$exoUser = Get-EXOMailbox -Filter "PrimarySmtpAddress -eq '$exoMailbox'"
# Construct and output a custom object with the properties of interest.
[pscustomobject] #{
UserName = $exoUser.DisplayName
UserId = $exoUser.Identity
# === Add the additional properties here:
'Searched User' = $exoUser.UserName
'SharePoint URL' = $spUrl
'Searched User GMID' = $exoUser.UserId
'Site Owner' = $spositeOwner
# ...
}
}
Note:
The above shows only some of the properties from your question; add as needed (it is unclear where $u comes from in some of them.
Using a custom-object literal ([pscustomobject] #{ ... }) is not only easier and more efficient than a New-Object PSObject -Property #{ ... }[1] call, unlike the latter it implicitly preserves the definition order of the properties, so that there's no need for an additional Select-Object call that ensures the desired ordering of the properties.
[1] Perhaps surprisingly, PSObject ([psobject]) and PSCustomObject ([pscustomobject]) refer to the same type, namely System.Management.Automation.PSObject, despite the existence of a separate System.Management.Automation.PSCustomObject, which custom-objects instances self-report as (([pscustomobject] #{}).GetType().FullName) - see GitHub issue #4344 for background information.
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
Purpose - I am trying to start vm if it is in stop state
Note - user will input Vm name , if its stop then it will start otherwise it will pop up that server already in started state.
$user = 'tooltest' #Vmname
$rg = Get-AzureRmResourceGroup
$data= $rg.ResourceGroupName
foreach ( $d in $data){
$res = Get-AzureRmResource | Where-Object {$_.ResourceGroupName -eq $d}
if ( $res.Name -eq $user){
Write-Output $res.Name
Write-Output $res.ResourceGroupName
$gg = Get-AzureRmVM -ResourceGroupName [string]$res.ResourceGroupName -Name $user -Status
If i m trying to print $res.ResourceGroupName - i am getting output of resourcegroup name 26 times (26 resources i have in That RG)
I wanted to print RG only one time , can anybody help me on that
One way to filter your output is just to select how many objects do you want with the Select-Object cmdlet.
Write-Output $res.ResourceGroupName | Select -First 1
I am working on query user command in PowerShell to filter the content to get the users who wer disconnected for more than 2 days on the server.
This is my result:
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
a_admin 2 Disc 20+16:56 19.08.2015
b_admin 3 Disc . 10.12.2015
c_admin 4 Disc 5+22:33 24.08.2015
d_admin 5 Disc 17:47 17.12.2015
e_admin 6 Disc 101+18:58 02.09.2015
f_admin 7 Disc 1+01:27 14.12.2015
The problem is the query user don't retrieve the data as an object format, so I can't select any column from these data, can any one help me to find a way to filter this content? Also, I am having a problem in the content of the idle time. It seems weird!?
I tried to put the output in a text file then get the content back and do some filtration, but the result is the same (USERNAME with empty records).
query user produces string output. You can't convert that to objects by piping it into Format-Table. And Select-Object won't do with the output of Format-Table what you seem to expect anyway.
Use a regular expression match to transform the string output into a list of objects:
$server = 'servername'
$re = '(\w+)\s+?(\S*)\s+?(\d+)\s+Disc\s+(\S+)\s+(\d+\.\d+\.\d+)'
query user /server:$server | Where-Object { $_ -match $re } | ForEach-Object {
New-Object -Type PSCustomObject -Property #{
'Username' = $matches[1]
'SessionID' = $matches[3]
'IdleTime' = $matches[4]
'LogonTime' = $matches[5]
}
} | Select-Object Username, IdleTime
This will give you everything as string values, though. Since you want to filter on the idle time you may want to convert the values to appropriate types. Using a more elaborate regular expression (with named groups) will help with that.
$server = 'servername'
$re = '(?<username>\w+)\s+?' +
'(\S*)\s+?' +
'(?<session>\d+)\s+' +
'Disc\s+' +
'(?:(?:(?<days>\d+)\+)?(?<hours>\d+):)?(?<minutes>\d+)\s+' +
'(?<logon>\d+\.\d+\.\d+)'
query user /server:$server | Where-Object { $_ -match $re } | ForEach-Object {
New-Object -Type PSCustomObject -Property #{
'Username' = $matches['username']
'SessionID' = [int]$matches['session']
'IdleTime' = if ($matches['days']) {
New-TimeSpan -Days $matches['days'] -Hours $matches['hours'] -Minutes $matches['minutes']
} elseif ($matches['hours']) {
New-TimeSpan -Hours $matches['hours'] -Minutes $matches['minutes']
} else {
New-TimeSpan -Minutes $matches['minutes']
}
'LogonTime' = [DateTime]::ParseExact($matches['logon'], 'dd\.MM\.yyyy', [Globalization.CultureInfo]::InvariantCulture)
}
} | Where-Object {
$_.IdleTime.TotalDays -gt 2
} | Select-Object Username, IdleTime
I am having an issue with the way that my ping results "roll" out on the screen. I am using this code:
$servers = "192.168.2.10","192.168.2.80","192.168.2.254"
$collection = $()
foreach ($server in $servers)
{
$status = #{ "ServerName" = $server; "TimeStamp" = (Get-Date -f s) }
$testconnection = (Test-Connection $server -Count 1 -ea 0)
$response = ($testconnection | select ResponseTime)
if ($response)
{
$status["Results"] = "Up"
$status["Responsetime"] = $response
}
else
{
$status["Results"] = "Down"
}
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus
$collection += $serverStatus
}
$collection | Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
I would to like create a loop for the ResponseTime
The code that I am using now gives one response.
When I give a count of 2, it prints the ResponseTime next to eachother per IP-adres.
Output:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:30:17 {#{ResponseTime=6}, #{ResponseTime=4}} Up 192.168.2.10
2014-10-22T23:30:18 Down 192.168.2.80
2014-10-22T23:30:25 {#{ResponseTime=1}, #{ResponseTime=3}} Up 192.168.2.254
What I want is, that the script prints each ResponseTime under eachother like this:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:11:50 #{ResponseTime=419} Up 192.168.2.10
2014-10-22T23:11:51 #{ResponseTime=415} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:52 #{ResponseTime=470} Up 192.168.2.254
2014-10-22T23:11:52 #{ResponseTime=7} Up 192.168.2.254
Or like this:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:11:50 #{ResponseTime=419} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:51 #{ResponseTime=415} Up 192.168.2.254
2014-10-22T23:11:52 #{ResponseTime=470} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:52 #{ResponseTime=7} Up 192.168.2.254
It doesn't matter which one, my preference is the second one
Could you please help me with this matter. Even if it is not possible tell me aswell.
Thank you,
Chris
I'll chime in late, not because the other answer are wrong by any means, they are both functional, but more so because nobody has pointed out that you are recreating the wheel.
You test the connection, and specify an erroraction for it that silently continues leaving your variable null. Then you have to test to see if the variable has results, and treat it one way, or if it doesn't treat it another way. What you have just done is made your own Try/Catch scenario. If you actually use the error to stop you can use the built in Try/Catch. Consider this approach:
$servers = "www.google.com","localhost","www.amazon.com"
$collection = #()
foreach ($server in $servers)
{
Try{
$testconnection = Test-Connection $server -Count 2 -ErrorAction Stop
$testconnection | ForEach{$collection += New-Object PSObject -Property ([ordered]#{
'TimeStamp' = Get-Date -Format s
'Server' = $server
'ResponseTime' = $_.responsetime
'Results' = 'Up'})
}
}
Catch{
$collection += New-Object PSObject -Property ([ordered]#{
'TimeStamp' = Get-Date -Format s
'Server' = $server
'ResponseTime' = $null
'Results' = 'Unreachable'
})
}
}
$collection #| Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
That tries to ping the server, and if it can it adds a custom object to the $collection array with the desired information. If the ping fails it also adds an object to the $collection showing that the server was unreachable.
Also, you had $collection = $(). I assume you were trying to create an empty array, which is correctly done $collection = #() (corrected in my suggested code). Now, I commented out the Export-CSV so I could see the results. This is what I saw:
TimeStamp Server ResponseTime Results
--------- ------ ------------ -------
2014-10-22T17:54:22 www.google.com 9 Up
2014-10-22T17:54:22 www.google.com 12 Up
2014-10-22T17:54:23 localhost 0 Up
2014-10-22T17:54:23 localhost 0 Up
2014-10-22T17:54:27 www.amazon.com Unreachable
Amazon didn't let me ping it, so it shows as unreachable.
Moving on to why your desired results are not practical... What you describe shows you pinging your servers and getting results from them at non-consecutive times. To do that you would have to do -count 1, and loop through the ForEach loop twice, so it would ping server 1 for 1 result, then server 2 for 1 result, then server 3 for 1 result. Then it would go back and ping server 1 for a second result, then server 2 for a second result, and then server 3 for a second result. If you wanted to do that you could I suppose, and it should give you your desired results, you would have to do something like this:
$servers = "www.google.com","localhost","www.amazon.com"
$collection = #()
$count = 2
for($i=1;$i -le $count;$i++){
ForEach($server in $servers){
do stuff to ping servers as described above, except change -count to 1
}
}
$collection | export-CSV '.\ServerStatus.csv' -notype
That will give you your desired results, but it is slower. If you have to run this against more than a few servers it will be noticeably slower. For just those three servers listed it made the entire process go from taking 3.7240945 seconds to taking 7.6104075 seconds (roughly double).
Instead of
$response = ($testconnection | select ResponseTime)
if ($response)
{
$status["Results"] = "Up"
$status["Responsetime"] = $response
}
do
if($testconnection)
{
$testconnection | % {
$status = #{"ServerName" = $server; "TimeStamp" = (Get-Date -f s); "Results" = "Up"; "Responsetime"= $_.responsetime};
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus;
$collection += $serverStatus }
}
else
{
$status = #{"ServerName" = $server; "TimeStamp" = (Get-Date -f s); "Results" = "Down"};
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus;
$collection += $serverStatus
}
The problem is that $testconnection or in your case $response is an array if the count of Test-Connection is greater then 1, so you have to loop through it and add the single entries to your collection.
Also to get the Value instead of the gibberish you get you have to call the .responsetime property.
In hopes I didn't make it too complicated I present this solution
$servers = "10.50.10.100","8.8.8.8","169.254.54.1"
$servers | ForEach-Object{
$server = $_
$timeStamp = (Get-Date -f s)
$testconnection = Test-Connection $server -Count 2 -ErrorAction 0
If(!$testconnection){
$props = #{
Server = $server
TimeStamp = $timeStamp
ResponseTime = ""
Results = "Down"
}
New-Object -TypeName PSObject -Property $props
} Else {
$testconnection | ForEach-Object{
$_ | Select-Object #{l='Server';e={$server}},#{l='TimeStamp';e={$timeStamp}},#{l='ResponseTime';e={$_.ResponseTime}},#{l='Results';e={"Up"}}
}
}
} | Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
So your logic is still here but as you can see some things have been changed. Paul was right, in that you needed to loop for each ResponseTime element you had. I also have done that but with a different approach that, if nothing else, will show you some of the Power in PowerShell. A break down of the code
Pipe $servers into a ForEach-Object. ForEach in works fine however I wanted to skip the saving the variables and just output straight to Export-CSV which is why I changed it.
So if you use Test-Connection on a server that does not exist or errors for some reason then you need to create an object to represent that. Using the desired properties, build a object with required values. This is output to the pipe instead of using a temporary variable.
When a connection test is successful then we need to output a number or variables to match the number of returns.
Continuing from #3 we use Select-Object to output the desired values. l stand for label and e for expression. Yes you could easily just use another $props variable. Just illustrating another option.
Since we changed the ForEach in the first step we can just output straight to Export-CSV
Sample output
Server TimeStamp ResponseTime Results
------ --------- ------------ -------
10.50.10.100 2014-10-22T20:22:01 0 Up
10.50.10.100 2014-10-22T20:22:01 0 Up
8.8.8.8 2014-10-22T20:22:02 43 Up
8.8.8.8 2014-10-22T20:22:02 39 Up
169.254.54.1 2014-10-22T20:22:03 Down