I bumped into a requirement to run a SysLog relay on one of my Windows 2008 R2 systems. After poking around on Google, and after getting a bit bored with the third-party offerings, I threw together a simple server of my own.

There is plenty of room for improvement here, but it works (for me at least) as it stands.

Written for and tested under PowerShell 2.0. The script could probably use some error handling.

SysLog.ps1 # # A basic SysLog server. Behaviour should be fairly consistent with # RFC 3164 (http://www.ietf.org/rfc/rfc3164.txt). # Network Configuration $SysLogPort = 514 # Default SysLog Port $Buffer = New-Object Byte[] 1024 # Maximum SysLog message size # Server Configuration $EnableMessageValidation = $True # Enable check of the PRI and Header $EnableRelay = $True # Enable relay to $RelayTargetIP $EnableLocalLogging = $True # Enable local logging of received messages $EnableConsoleLogging = $False # Enable logging to the console $EnableHostNameLookup = $True # Lookup hostname for connecting IP $EnableHostNamesOnly = $True # Uses Host Name only instead of FQDNs $RelayTargetIP = "10.0.0.1" # Must be an IP Address $LogFolder = "C:SysLogLogFiles" # Path must exist # Global variables used to store day and date-stamp for log roll-over $Day = (Get-Date).Day $DateStamp = (Get-Date).ToString("yyyy.MM.dd") # Relay Initialisation If ($EnableRelay) { $RelayTarget = [Net.IPAddress]::Parse($RelayTargetIP) $RelayTargetEndPoint = New-Object Net.IPEndPoint($RelayTarget, $SysLogPort) } # A launcher for the process # # Caller: Manual / Script Function Start-SysLog { $Socket = CreateSocket StartReceive $Socket } # Create and bind to the socket # # Caller: Start-SysLog Function CreateSocket { $Socket = New-Object Net.Sockets.Socket( [Net.Sockets.AddressFamily]::Internetwork, [Net.Sockets.SocketType]::Dgram, [Net.Sockets.ProtocolType]::Udp) $ServerIPEndPoint = New-Object Net.IPEndPoint( [Net.IPAddress]::Any, $SysLogPort) $Socket.Bind($ServerIPEndPoint) Return $Socket } # Recieve a single message # # Caller: Start-SysLog Function StartReceive([Net.Sockets.Socket]$Socket) { # Placeholder to store source of incoming packet $SenderIPEndPoint = New-Object Net.IPEndPoint([Net.IPAddress]::Any, 0) $SenderEndPoint = [Net.EndPoint]$SenderIPEndPoint $ServerRunning = $True While ($ServerRunning -eq $True) { $BytesReceived = $Socket.ReceiveFrom($Buffer, [Ref]$SenderEndPoint) $Message = $Buffer[0..$($BytesReceived - 1)] $Message = ValidateMessage $Message $SenderEndPoint.Address.IPAddressToString If ($EnableRelay) { RelayMessage $Socket $Message } } } # Relay the message to an upstream SysLog server. Either basic forwarding, # or full validation. # # Caller: StartReceive Function RelayMessage([Net.Sockets.Socket]$Socket, [Byte[]]$Message) { [Void]$Socket.SendTo($Message, $RelayTargetEndPoint) } # Check the validity of the message (if option is enabled). Adjust message # according to recommendations in RFC 3164. # # Caller: StartReceive Function ValidateMessage([Byte[]]$Message, [String]$HostName) { If ($EnableMessageValidation) { $MessageString = [Text.Encoding]::ASCII.GetString($Message) If (IsValidPRI($MessageString)) { If (!(IsValidDateTime($MessageString))) { $PRI = [Int]($MessageString -Replace "<|>.") $HostName = GetHostName $HostName $MessageString = "<$PRI>$(NewDateTimeString) $HostName $MessageString" $Message = EncodeMessage $MessageString } } Else { $HostName = GetHostName $HostName $MessageString = "<13>$(NewDateTimeString) $HostName $MessageString" $Message = EncodeMessage $MessageString } } If ($EnableLocalLogging -Or $EnableConsoleLogging) { If ($MessageString -eq $Null) { $MessageString = [Text.Encoding]::ASCII.GetString($Message) } If ($EnableLocalLogging) { WriteToLog $MessageString $HostName } If ($EnableConsoleLogging) { Write-Host $MessageString } } Return $Message } # Validate the PRI (Priority Field - Facility and Severity) # No parsing is performed. No network prioritisation is implemented # # Caller: ValidateMessage Function IsValidPRI([String]$MessageString) { If ($MessageString.SubString(0, 1) -ne "<") { Return $False } If (!$MessageString.SubString(2, 3).Contains(">")) { Return $False } $PRI = [Int]($MessageString -Replace "<|>.") # PRI = (Facility * 8) + Severity. Maximum and minimum values from RFC 3164 If ($PRI -lt 1 -Or $PRI -gt 191) { Return $False } Return $True } # Validate the TimeStamp formatting # # Caller: ValidateMessage Function IsValidDateTime([String]$MessageString) { $IsValid = $False If ($MessageString -Match "(?<=>)w{3}ss?d{1,2}s(dd:){2}dd(?=s)") { $Date = New-Object DateTime ForEach ($Format in @("MMM d hh:mm:ss", "MMM dd hh:mm:ss")) { $Date = New-Object DateTime $IsValid = [DateTime]::TryParseExact( $Matches[0], $Format, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::AssumeUniversal, [Ref]$Date) If ($IsValid) { Return $True } } } Return $False } # Create a new DateTime String # # Caller: ValidateMessage Function NewDateTimeString { $Date = (Get-Date).ToUniversalTime() If ($Date.Day -lt 10) { Return $Date.ToString("MMM d HH:mm:ss") } Return $Date.ToString("MMM dd HH:mm:ss") } # Attempt to lookup the HostName if an IP value was passed. # [Net.Dns]::GetHostEntry fails to return if a Forward Lookup record # does not exist. NsLookup as a simple alternative. # # Caller: ValidateMessage Function GetHostName([String]$HostName) { If (!$EnableHostNameLookup) { Return $HostName } If ([Net.IPAddress]::TryParse($HostName, [Ref]$Null)) { $Temp = (nslookup -q=ptr $HostName | ?{ $_ -Like "*name = *" }) $Temp = $Temp -Replace ".*name = " If ($Temp -ne [String]::Empty) { $HostName = $Temp } } If ($EnableHostNamesOnly) { Return $HostName.Split(".")[0] } Return $HostName } # Returns a Byte Array representation of the original message. # If the length is greater than 1024 Bytes the array is truncated # as stipulated under RFC 3164. # # Caller: ValidateMessage Function EncodeMessage([String]$MessageString) { $Message = [Text.Encoding]::ASCII.GetBytes($MessageString) If ($Message.Length -gt 1024) { Return $Message[0..1023] } Return $Message } # Maintain a per-host log file in the $LogFolder # Script does not clean up old log files # # Caller: ValidateMessage Function WriteToLog([String]$MessageString, [String]$HostName) { # Simple time based roll-over check If ((Get-Date).Day -ne $Day) { $Day = (Get-Date).Day $DateStamp = (Get-Date).ToString("yyyy.MM.dd") } $LogFile = "$LogFolder$HostName-$DateStamp.log" $MessageString >> $LogFile } # Start the server Start-SysLog