Finding remote or local login events and types using PowerShell

11 minute read

Does anyone actually like scrolling through the Event Viewer? Well the filtering is nice, but why make yourself do all that work? Besides, what is login type 7 or how do you filter for specific users? Let me show you in PowerShell :wink: (no idea if that wink will actually show up or just be :wink:)

Pop quiz

Ok, quick, riddle me these:

  • What is the event ID for logins?
  • What event log do login events land in?
  • What types of logins are there and what are their types?

If you’re answer was, let me look it up, then great! You’ve got too many other to worry about to try to memorize this stuff.

But, real quick, since we are going to cover this, here are the answers that we’ll need in pulling events from the event log:

source

  • Event ID for logins: 4624 (Since Vista)
  • Event log: Security
  • Logon types:
Logon Type Logon Title Description
2 Interactive A user logged on to this computer.
3 Network A user or computer logged on to this computer from the network.
4 Batch Batch logon type is used by batch servers, where processes may be executing on behalf of a user without their direct intervention.
5 Service A service was started by the Service Control Manager.
7 Unlock This workstation was unlocked.
8 NetworkCleartext A user logged on to this computer from the network. The user’s password was passed to the authentication package in its unhashed form. The built-in authentication packages all hash credentials before sending them across the network. The credentials do not traverse the network in plaintext (also called cleartext).
9 NewCredentials A caller cloned its current token and specified new credentials for outbound connections. The new logon session has the same local identity, but uses different credentials for other network connections.
10 RemoteInteractive A user logged on to this computer remotely using Terminal Services or Remote Desktop.
11 CachedInteractive A user logged on to this computer with network credentials that were stored locally on the computer. The domain controller was not contacted to verify the credentials.

To build a tool or not to build a tool…

That is a dumb question!

Get-WinEvent refresher

If you remember from tracking down lockouts or even tracking down bad password attempts, then you should know about Get-WinEvent. You’ve hopefully also picked up that we can use this cmdlet to write TONS of useful functions for pulling information about a computer!

So just a quick refresher on the base command we’ll be using:

Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    ID = 4624
}

This will return all events from the Security event log that have an ID of 4624. And, just as I was reminded of when I tested that command, you need to be running as an administrator to access the Security logs.

Dealing with the data

When you run that command, you’ll notice that you get a large number of entries. I got 55 in the last hour, one of which was me logging into my computer and another one was me unlocking my computer after going to the bathroom. But that doesn’t account for the 53 other events, so lets turn that into some useful data to find out.

Properties

So like my previous posts on getting specific data from the event logs, we are going to take a look at the properties of the event object that is returned. This is so similar to before, I’m not going to go in depth again, I’ll just show you what it looks like:

Here is the message

An account was successfully logged on.

Subject:
        Security ID:            S-1-5-18
        Account Name:           ALPHAWOLF$
        Account Domain:         HOWELLIT
        Logon ID:               0x3E7

Logon Information:
        Logon Type:             2
        Restricted Admin Mode:  -
        Virtual Account:                No
        Elevated Token:         No

Impersonation Level:            Impersonation

New Logon:
        Security ID:            S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXX-XXXX
        Account Name:           Anthony
        Account Domain:         ALPHAWOLF
        Logon ID:               0xBCB48FD
        Linked Logon ID:                0xBCB48E0
        Network Account Name:   -
        Network Account Domain: -
        Logon GUID:             {00000000-0000-0000-0000-000000000000}

Process Information:
        Process ID:             0xb58
        Process Name:           C:\Windows\System32\svchost.exe

Network Information:
        Workstation Name:       ALPHAWOLF
        Source Network Address: 127.0.0.1
        Source Port:            0

Detailed Authentication Information:
        Logon Process:          User32
        Authentication Package: Negotiate
        Transited Services:     -
        Package Name (NTLM only):       -
        Key Length:             0

This event is generated when a logon session is created. It is generated on the computer that was accessed.

The subject fields indicate the account on the local system which requested the logon. This is most commonly a service such as the Server service, or a local process such as Winlogon.exe or Services.exe.

The logon type field indicates the kind of logon that occurred. The most common types are 2 (interactive) and 3 (network).

The New Logon fields indicate the account for whom the new logon was created, i.e. the account that was logged on.

The network fields indicate where a remote logon request originated. Workstation name is not always available and may be left blank in some cases.

The impersonation level field indicates the extent to which a process in the logon session can impersonate.

The authentication information fields provide detailed information about this specific logon request.
        - Logon GUID is a unique identifier that can be used to correlate this event with a KDC event.
        - Transited services indicate which intermediate services have participated in this logon request.
        - Package name indicates which sub-protocol was used among the NTLM protocols.
        - Key length indicates the length of the generated session key. This will be 0 if no session key was requested.

You’ll notice a lot of good information there, but, spoiler alert, the easiest to access is in the Properties property (unless you want to get into XPath, check out Mathias Jessen’s post):

Value
-----
S-1-5-18
ALPHAWOLF$
HOWELLIT
999
S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXX-XXXX
Anthony
ALPHAWOLF
197871869
2
User32
Negotiate
ALPHAWOLF
00000000-0000-0000-0000-000000000000
-
-
0
2904
C:\Windows\System32\svchost.exe
127.0.0.1
0
%%1833
-
-
-
%%1843
197871840
%%1843

So we can do the same thing as before and create a custom object from that data. And we can even convert the logon type from a number to the actual type so you don’t have to reference the table every time!

Logon types

If you remember from by Get-ADUserBadPasswords post, I used a hashtable to match logon types to definitions. That works, but an enum is easier to read, so we’ll use that this time:

enum LogonTypes {
    Interactive = 2
    Network = 3
    Batch = 4
    Service = 5
    Unlock = 7
    NetworkClearText = 8
    NewCredentials = 9
    RemoteInteractive = 10
    CachedInteractive = 11
}

And once we have that enum, we can use it to determine the logon types (Notice how clean this is compared to using a hashtable?):

PS> [LogonTypes]2
Interactive

Objectifying the event

So with all of that information, we can grab all of the events:

$events = Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    ID = 4624
}

Loop through them and create an output object:

foreach ($event in $events) {
    [pscustomobject]@{
        UserAccount = $event.Properties.Value[5]
        UserDomain = $event.Properties.Value[6]
        LogonType = [LogonType]$event.Properties.Value[8]
        WorkstationName = $event.Properties.Value[11]
        SourceNetworkAddress = $event.Properties.Value[19]
    }
}

Writing the function

Awesome! We made it. Lets make this into a function!

Function Get-LoginEvents {
    Param (
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias('Name')]
        [string]$ComputerName = $env:ComputerName
        ,
        [datetime]$StartTime
        ,
        [datetime]$EndTime
    )
    Begin {
        enum LogonTypes {
            Interactive = 2
            Network = 3
            Batch = 4
            Service = 5
            Unlock = 7
            NetworkClearText = 8
            NewCredentials = 9
            RemoteInteractive = 10
            CachedInteractive = 11
        }
        $filterHt = @{
            LogName = 'Security'
            ID = 4624
        }
        if ($PSBoundParameters.ContainsKey('StartTime')){
            $filterHt['StartTime'] = $StartTime
        }
        if ($PSBoundParameters.ContainsKey('EndTime')){
            $filterHt['EndTime'] = $EndTime
        }
    }
    Process {
        Get-WinEvent -ComputerName $ComputerName -FilterHashtable $filterHt | foreach-Object {
            [pscustomobject]@{
                ComputerName = $ComputerName
                UserAccount = $_.Properties.Value[5]
                UserDomain = $_.Properties.Value[6]
                LogonType = [LogonTypes]$_.Properties.Value[8]
                WorkstationName = $_.Properties.Value[11]
                SourceNetworkAddress = $_.Properties.Value[19]
                TimeStamp = $_.TimeCreated
            }
        }
    }
    End{}
}

Something you should take note of is that in this function I went from:

$events = Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    ID = 4624
}
foreach ($event in $events) {
    [pscustomobject]@{
        ComputerName = $ComputerName
        UserAccount = $event.Properties.Value[5]
        UserDomain = $event.Properties.Value[6]
        LogonType = [LogonType]$event.Properties.Value[8]
        WorkstationName = $event.Properties.Value[11]
        SourceNetworkAddress = $event.Properties.Value[19]
    }
}

To using:

Get-WinEvent -ComputerName $ComputerName -FilterHashtable $filterHt | foreach-Object {
    [pscustomobject]@{
        ComputerName = $ComputerName
        UserAccount = $_.Properties.Value[5]
        UserDomain = $_.Properties.Value[6]
        LogonType = [LogonTypes]$_.Properties.Value[8]
        WorkstationName = $_.Properties.Value[11]
        SourceNetworkAddress = $_.Properties.Value[19]
    }
}

This is because the second method leverages the pipeline to immediately start receiving output instead of enumerating all the objects and then returning them one by one. So the second method is faster if you are running the command from an interactive prompt. Be sure to test it out yourself if you don’t believe me!

Usage

The simplest usage is just running the cmdlet naked:

Get-LoginEvents

This will return and parse all login security events on your local system. This could be quite a few! So I encourage you to use the date filtering:

Get-LoginEvents -StartTime (Get-Date).AddHours(-1)

Or even pipe in to Where-Object to filter for certain types of logons:

Get-LoginEvents -StartTime (Get-Date).AddDays(-1) | ? LogonType -eq 'Interactive'

And since we did enable pipeline input, you can pass data from a string array:

Get-Content C:\path\to\computers.txt | Get-LoginEvents -StartTime (Get-Date).AddDays(-1) -EndTime (Get-Date).AddDays(-1).AddHours(1)

Or even pipe Get-ADComputer to the cmdlet:

Get-ADComputer -Filter {Name -like 'IT*'} | Get-LoginEvents -StartTime (Get-Date).AddDays(-7) | Where-Object LogonType -eq 'Interactive'

Conclusion

I hope you find this useful and that it saves you some time! I’ve used this cmdlet to remind myself when I logged into my computer to accurately track my time, to track malware logins across networks, to troubleshoot service account login issues, etc. There is so much you can do with it.

I’ve also uploaded this to my Utilities repo as well as the last post since I discovered that I never pushed my changes. But I have now!

If you’ve got any feedback, let me know with a comment, tweet, email, or whatever. I’m happy to assist if you have trouble using any of the tools I write.

Leave a Comment