├── Export-Logons-ToPgSQL.psm1 ├── Get-LogonHistory.ps1 ├── LICENSE.txt ├── README.md └── logon_script.txt /Export-Logons-ToPgSQL.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Takes logon and logoff data and inserts it into a PostgreSQL database. 4 | .Description 5 | This function uses the PostgreSQL ODBC driver and a pre-configured PostgreSQL Data Source to establish a connection to a PostgreSQL database. 6 | 7 | It then takes input from arguments on the command line or from the pipeline and attempts to insert them into a database containing logon/logoff data. 8 | 9 | The function expects/assumes the following database schema: 10 | 11 | TABLE logons(username VARCHAR(20), compname VARCHAR(20), 12 | logontype VARCHAR(10), action VARCHAR(10), date DATE, 13 | time TIME, PRIMARY KEY (username,compname,action,date,time)) 14 | .Inputs 15 | This function takes the following inputs. These can also be passed in by property name via the pipeline: 16 | 17 | [String]UserName 18 | The username of the account that logged on/off of the machine. 19 | [String]ComputerName 20 | The name of the computer that the user logged on to/off of. 21 | [String]Action 22 | The action the user took with regards to the computer. Either 'logon' or 'logoff'. 23 | [String]LogonType 24 | Either 'console' or 'remote', depending on how the user logged on. This property is ignored if the value of action is 'logoff'. 25 | [DateTime]TimeStamp 26 | A DateTime object representing the date and time that the user logged on/off. 27 | .Outputs 28 | Export-Logons-ToPgSQL writes a summary of changes to the console window, indicating how many new rows were inserted, how many rows were already in the database (and therefore rejected), and how many inserts failed due to some other error. 29 | 30 | .Example 31 | .\Get-LogonHistory.ps1 | Export-Logons-ToPgSQL 32 | 33 | Description 34 | ----------- 35 | Gets the available logon entries in the Security log on the local computer, then imports them into the PostgreSQL database. 36 | .Example 37 | Invoke-Command -ComputerName 'remotecomputer' -File '.\Get-LogonHistory.ps1' | Export-Logons-ToPgSQL 38 | 39 | Description 40 | ----------- 41 | Gets the available logon entries in the Security log on a remote computer named 'remotecomputer', then imports them into the PostgreSQL database. 42 | 43 | .Example 44 | Export-Logons-ToPgSQL -UserName 'bob' -ComputerName 'LAB1' -LogonType 'console' -Action 'logon' -TimeStamp [DateTime]'2013-02-10 10:50:00' 45 | 46 | Description 47 | ----------- 48 | Inserts a manually crafted logon entry into the PostgreSQL database. 49 | #> 50 | function Export-Logons-ToPgSQL { 51 | [CmdletBinding()] 52 | param( 53 | [Parameter(Mandatory=$true, 54 | ValueFromPipelineByPropertyName=$True)] 55 | [String]$UserName, 56 | [Parameter(Mandatory=$true, 57 | ValueFromPipelineByPropertyName=$True)] 58 | [String]$ComputerName, 59 | [Parameter(Mandatory=$true, 60 | ValueFromPipelineByPropertyName=$True)] 61 | [String]$Action, 62 | [Parameter(Mandatory=$false, 63 | ValueFromPipelineByPropertyName=$True)] 64 | [String]$LogonType, 65 | [Parameter(Mandatory=$true, 66 | ValueFromPipelineByPropertyName=$True)] 67 | [DateTime]$TimeStamp, 68 | [Parameter(Mandatory=$false, 69 | ValueFromPipelineByPropertyName=$false)] 70 | [String]$LogFile = '' 71 | ) 72 | 73 | BEGIN { 74 | # Check that log file path is valid, if given. 75 | if ($LogFile -eq '') { 76 | $logToFile = $false 77 | } else { 78 | try { 79 | $pathToLogFile = Split-Path -Parent -Path $LogFile 80 | } catch { 81 | Write-Error $_ 82 | break 83 | } 84 | if (Test-Path $pathToLogFile) { 85 | $logToFile = $true 86 | } else { 87 | Write-Error "Invalid path to log file specified." 88 | break 89 | } 90 | } 91 | 92 | # If logging, begin writing to logfile. 93 | if ($logToFile) { 94 | $date = Get-Date 95 | "[$date]" | Out-File -FilePath $LogFile -Encoding ASCII -Append -NoClobber 96 | } 97 | 98 | # Create and prep DB connection objects and establish connection. 99 | $DBConn = New-Object System.Data.Odbc.OdbcConnection('DSN=Pgsql_logondb') 100 | $DBCmd = $DBConn.CreateCommand() 101 | $DBCmd.CommandText = 'INSERT INTO logons (username,compname,logontype,action,date,time)' 102 | $DBCmd.CommandText += ' VALUES (?,?,?,?,?,?);' 103 | [void]$DBCmd.Parameters.Add('@username', [System.Data.Odbc.OdbcType]::varchar, 20) 104 | [void]$DBCmd.Parameters.Add('@compname', [System.Data.Odbc.OdbcType]::varchar, 20) 105 | [void]$DBCmd.Parameters.Add('@logontype', [System.Data.Odbc.OdbcType]::varchar, 10) 106 | [void]$DBCmd.Parameters.Add('@action', [System.Data.Odbc.OdbcType]::varchar, 10) 107 | [void]$DBCmd.Parameters.Add('@date', [System.Data.Odbc.OdbcType]::date) 108 | [void]$DBCmd.Parameters.Add('@time', [System.Data.Odbc.OdbcType]::time) 109 | $newRows = $oldRows = $errRows = 0 110 | try { 111 | $DBCmd.Connection.Open() 112 | } catch { 113 | if ($logToFile) { 114 | $_ | Out-File -FilePath $LogFile -Encoding ASCII -Append -NoClobber 115 | } else { 116 | Write-Error $_ 117 | } 118 | break 119 | } 120 | } 121 | 122 | PROCESS { 123 | # Parse data and attempt to insert a row into the database. 124 | [DateTime]$date = $TimeStamp.date 125 | [TimeSpan]$time = Get-Date $TimeStamp -Format 'HH:mm:ss' 126 | $DBCmd.Parameters['@username'].Value = $UserName 127 | $DBCmd.Parameters['@compname'].Value = $ComputerName 128 | $DBCmd.Parameters['@logontype'].Value = $LogonType 129 | $DBCmd.Parameters['@action'].Value = $Action 130 | $DBCmd.Parameters['@date'].Value = $date 131 | $DBCmd.Parameters['@time'].Value = $time 132 | try { 133 | [void]$DBCmd.ExecuteNonQuery() 134 | $newRows = $newRows + 1 135 | } catch { 136 | $uniqueErr = '*ERROR `[23505`] ERROR: duplicate key value violates unique constraint*' 137 | if ($_.exception -like $uniqueErr) { 138 | $oldRows = $oldRows + 1 139 | } else { 140 | if ($logToFile) { 141 | $_ | Out-File -FilePath $LogFile -Encoding ASCII -Append -NoClobber 142 | } else { 143 | Write-Error $_ 144 | } 145 | $errRows = $errRows + 1 146 | } 147 | } 148 | } 149 | 150 | END { 151 | $DBCmd.Connection.Close() 152 | if ($logToFile) { 153 | "$newRows new rows written to database." | Out-File -FilePath $LogFile -Encoding ASCII -Append -NoClobber 154 | "$oldRows existing rows discarded." | Out-File -FilePath $LogFile -Encoding ASCII -Append -NoClobber 155 | "$errRows rows failed to insert for unknown reasons." | Out-File -FilePath $LogFile -Encoding ASCII -Append -NoClobber 156 | } else { 157 | if ($newRows) { Write-Host "$newRows new rows written to database." } 158 | if ($oldRows) { Write-Host "$oldRows existing rows discarded." } 159 | if ($errRows) { Write-Host "$errRows rows failed to insert for unknown reasons." } 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /Get-LogonHistory.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Extracts recent logon history for the local machine from the Security Event Log. 4 | .Description 5 | This script scans through the Security Event Log on the local machine for interactive logons (both local and remote), and logouts. 6 | 7 | It then constructs a PowerShell Custom Object containing the fields of interest and writes that object to the pipeline as its output. 8 | 9 | NOTE: This script must be run 'As Administrator' in order to access the Security Event Log. 10 | 11 | To run this function on a remote computer, use it in conjunction with the Invoke-Command cmdlet. 12 | .Inputs 13 | None. You cannot pipe input to this script. 14 | .Outputs 15 | System.Management.Automation.PSCustomObject 16 | 17 | Get-LogonHistory returns a custom object containing the following properties: 18 | 19 | [String]UserName 20 | The username of the account that logged on/off of the machine. 21 | [String]ComputerName 22 | The name of the computer that the user logged on to/off of. 23 | [String]Action 24 | The action the user took with regards to the computer. Either 'logon' or 'logoff'. 25 | [String]LogonType 26 | Either 'console' or 'remote', depending on how the user logged on. This property is null if the user logged off. 27 | [DateTime]TimeStamp 28 | A DateTime object representing the date and time that the user logged on/off. 29 | .Notes 30 | 31 | .Example 32 | .\Get-LogonHistory.ps1 33 | 34 | Description 35 | ----------- 36 | Gets the available logon entries in the Security log on the local computer. 37 | .Example 38 | Invoke-Command -ComputerName 'remotecomputer' -File '.\Get-LogonHistory.ps1' 39 | 40 | Description 41 | ----------- 42 | Gets the available logon entries in the Security log on a remote computer named 'remotecomputer'. 43 | #> 44 | 45 | 46 | function Get-Win7LogonHistory { 47 | $logons = Get-EventLog Security -AsBaseObject -InstanceId 4624,4647 | 48 | Where-Object { ($_.InstanceId -eq 4647) ` 49 | -or (($_.InstanceId -eq 4624) -and ($_.Message -match "Logon Type:\s+2")) ` 50 | -or (($_.InstanceId -eq 4624) -and ($_.Message -match "Logon Type:\s+10")) } 51 | $poweroffs = Get-EventLog System -AsBaseObject -InstanceId 41 52 | $events = $logons + $poweroffs | Sort-Object TimeGenerated 53 | 54 | if ($events) { 55 | foreach($event in $events) { 56 | # Parse logon data from the Event. 57 | if ($event.InstanceId -eq 4624) { 58 | # A user logged on. 59 | $action = 'logon' 60 | 61 | $event.Message -match "Logon Type:\s+(\d+)" | Out-Null 62 | $logonTypeNum = $matches[1] 63 | 64 | # Determine logon type. 65 | if ($logonTypeNum -eq 2) { 66 | $logonType = 'console' 67 | } elseif ($logonTypeNum -eq 10) { 68 | $logonType = 'remote' 69 | } else { 70 | $logonType = 'other' 71 | } 72 | 73 | # Determine user. 74 | if ($event.message -match "New Logon:\s*Security ID:\s*.*\s*Account Name:\s*(\w+)") { 75 | $user = $matches[1] 76 | } else { 77 | $index = $event.index 78 | Write-Warning "Unable to parse Security log Event. Malformed entry? Index: $index" 79 | } 80 | 81 | } elseif ($event.InstanceId -eq 4647) { 82 | # A user logged off. 83 | $action = 'logoff' 84 | $logonType = $null 85 | 86 | # Determine user. 87 | if ($event.message -match "Subject:\s*Security ID:\s*.*\s*Account Name:\s*(\w+)") { 88 | $user = $matches[1] 89 | } else { 90 | $index = $event.index 91 | Write-Warning "Unable to parse Security log Event. Malformed entry? Index: $index" 92 | } 93 | } elseif ($event.InstanceId -eq 41) { 94 | # The computer crashed. 95 | $action = 'logoff' 96 | $logonType = $null 97 | $user = '*' 98 | } 99 | 100 | # As long as we managed to parse the Event, print output. 101 | if ($user) { 102 | $timeStamp = Get-Date $event.TimeGenerated 103 | $output = New-Object -Type PSCustomObject 104 | Add-Member -MemberType NoteProperty -Name 'UserName' -Value $user -InputObject $output 105 | Add-Member -MemberType NoteProperty -Name 'ComputerName' -Value $env:computername -InputObject $output 106 | Add-Member -MemberType NoteProperty -Name 'Action' -Value $action -InputObject $output 107 | Add-Member -MemberType NoteProperty -Name 'LogonType' -Value $logonType -InputObject $output 108 | Add-Member -MemberType NoteProperty -Name 'TimeStamp' -Value $timeStamp -InputObject $output 109 | Write-Output $output 110 | } 111 | } 112 | } else { 113 | Write-Host "No recent logon/logoff events." 114 | } 115 | } 116 | 117 | function Get-WinXPLogonHistory { 118 | $logons = Get-EventLog Security -AsBaseObject -InstanceId 528,551 | 119 | Where-Object { ($_.InstanceId -eq 551) ` 120 | -or (($_.InstanceId -eq 528) -and ($_.Message -match "Logon Type:\s+2")) ` 121 | -or (($_.InstanceId -eq 528) -and ($_.Message -match "Logon Type:\s+10")) } 122 | #$poweroffs = Get-Eventlog System -AsBaseObject -InstanceId 6008 123 | #$events = $logons + $poweroffs | Sort-Object TimeGenerated 124 | 125 | if ($events) { 126 | foreach($event in $events) { 127 | # Parse logon data from the Event. 128 | if ($event.InstanceId -eq 528) { 129 | # A user logged on. 130 | $action = 'logon' 131 | 132 | $event.Message -match "Logon Type:\s+(\d+)" | Out-Null 133 | $logonTypeNum = $matches[1] 134 | 135 | # Determine logon type. 136 | if ($logonTypeNum -eq 2) { 137 | $logonType = 'console' 138 | } elseif ($logonTypeNum -eq 10) { 139 | $logonType = 'remote' 140 | } else { 141 | $logonType = 'other' 142 | } 143 | 144 | # Determine user. 145 | if ($event.message -match "Successful Logon:\s*User Name:\s*(\w+)") { 146 | $user = $matches[1] 147 | } else { 148 | $index = $event.index 149 | Write-Warning "Unable to parse Security log Event. Malformed entry? Index: $index" 150 | } 151 | 152 | } elseif ($event.InstanceId -eq 551) { 153 | # A user logged off. 154 | $action = 'logoff' 155 | $logonType = $null 156 | 157 | # Determine user. 158 | if ($event.message -match "User initiated logoff:\s*User Name:\s*(\w+)") { 159 | $user = $matches[1] 160 | } else { 161 | $index = $event.index 162 | Write-Warning "Unable to parse Security log Event. Malformed entry? Index: $index" 163 | } 164 | }# elseif ($event.InstanceId -eq 6008) { 165 | # The computer crashed. 166 | # $action = 'logoff' 167 | # $logonType = $null 168 | # $user = '*' 169 | #} 170 | 171 | # As long as we managed to parse the Event, print output. 172 | if ($user) { 173 | $timeStamp = Get-Date $event.TimeGenerated 174 | $output = New-Object -Type PSCustomObject 175 | Add-Member -MemberType NoteProperty -Name 'UserName' -Value $user -InputObject $output 176 | Add-Member -MemberType NoteProperty -Name 'ComputerName' -Value $env:computername -InputObject $output 177 | Add-Member -MemberType NoteProperty -Name 'Action' -Value $action -InputObject $output 178 | Add-Member -MemberType NoteProperty -Name 'LogonType' -Value $logonType -InputObject $output 179 | Add-Member -MemberType NoteProperty -Name 'TimeStamp' -Value $timeStamp -InputObject $output 180 | Write-Output $output 181 | } 182 | } 183 | } else { 184 | Write-Host "No recent logon/logoff events." 185 | } 186 | } 187 | $OSversion = (Get-WmiObject -Query 'SELECT version FROM Win32_OperatingSystem').version 188 | if ($OSversion -ge 6) { 189 | Get-Win7LogonHistory 190 | } else { 191 | Get-WinXPLogonHistory 192 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 The Computer Action Team, Portland State University 2 | 3 | The Computer Action Team can be contacted at: support@cat.pdx.edu 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Get-LogonHistory 2 | ================ 3 | 4 | Gets recent logons/logouts from the Windows Security Log on the local 5 | machine, and outputs them as custom PowerShell objects. 6 | 7 | Usage: 8 | ------ 9 | .\Get-LogonHistory.ps1 10 | 11 | Use Get-Help .\Get-LogonHistory.ps1 for more information. 12 | 13 | Export-Logons-ToPgSQL 14 | ===================== 15 | 16 | A PowerShell module containing a function that takes logon data and 17 | pipes it into a PostgreSQL database. It was necessary to make this a 18 | function instead of a script so it could handle pipeline input. 19 | 20 | Usage: 21 | ------ 22 | To use the function, first import the module into your PowerShell 23 | session by using the Import-Module cmdlet, e.g. 24 | 25 | Import-Module '\\path\to\Export-Logons-ToPgSQL.psm1' 26 | 27 | You can then call the function as if it were a cmdlet itself, e.g. 28 | 29 | Export-Logons-ToPgSQL -UserName 'bob' -ComputerName 'LAB1' -LogonType 'console' ` 30 | -Action 'logon' -TimeStamp [DateTime]'2013-02-10 10:50:00' 31 | 32 | Requirements: 33 | ------------- 34 | ODBC drivers must be installed (these can be obtained from PostgreSQL.org) 35 | and an ODBC Data Source named Pgsql_logondb must be configured to connect to the 36 | target database. 37 | 38 | The script assumes the following database schema: 39 | 40 | TABLE logons(username VARCHAR(20), compname VARCHAR(20),\n 41 | logontype VARCHAR(10), action VARCHAR(10), date DATE,\n 42 | time TIME, PRIMARY KEY (username,compname,action,date,time)) 43 | 44 | Use Get-Help Export-Logons-ToPgSQL after importing the module for more 45 | information. -------------------------------------------------------------------------------- /logon_script.txt: -------------------------------------------------------------------------------- 1 | Windows 7: 2 | event id 4624: account logon 3 | event id 4647: account logoff 4 | 5 | Windows XP: 6 | event id 528: account logon 7 | event id 551: account logoff 8 | 9 | logon type 2: interactive 10 | logon type 3: network 11 | logon type 10: remote desktop --------------------------------------------------------------------------------