Recently, I have been troubleshooting radius. Radius hasn’t changed in decades. I say this lightingly too. The network policy server is a feature that helps with connecting things like unifi wifi and more. Each radius connection produces a reason code. Today we want to look through the radius logs and get as much useful information without screaming at the screen. Radius logs are a little daunting. This is why many people use an SQL server for the radius logs. However, if you are not one of those people who can do this, the traditional radius logging works wonders. So, we will read radius logs with PowerShell.
Radius Logging
Before we start, we need to know what we are dealing with. The standard location for readius logs is: C:\Windows\System32\LogFiles. You can change this location as you see fit. I personally changed my locations to a c:\logs\radius location. This helps me find it quicker and generally, I don’t have trouble guessing what is what. You can set the radius log location by doing the following:
Start Network Policy Server
Click account
Under Log File Properties click Change Log File Properties
A box will pop up called “Log File Properties” Click on the “Log File” tab.
This is where you can change your directory.
Change your Format to DTS Compliant. As this script works best with it.
I personally like smaller files when I am working with log searches. So I select the “When log file reaches this size:” I select 1 – 5 MB.
Click ok
Now your log files will live wherever you told them. You will need to change the script around a little if you are using a different location than me.
As you can tell, this script needs to be used on the server in question. However, You could wrap this script into a nice wrapper. That would allow you to execute on remote machines. The breakdown is very simple on this one as well. DTS is an XML format. Thus, you just need to use the [XML] before any of the lines. The XML is formatted with the event, it’s name, and the text. It’s a very simple setup. From there I select what I want and give it in a pscustom object. That’s it. Its a very simple setup. That’s why we should always read radius logs with Powershell.
While reading on Reddit, I found a common thread. People need a quick way to do a Share Point File Audit. I have a PowerShell function for this in my toolbox. This tool heavily uses the Search-UnifiedAuditLog command let. The most common items I tend to audit are file modifications and deletions. This function goes through, modified, moved, renamed, downloaded, uploaded, accessed, synced, malware detection, restored from trash, locked, and finally unlocked. The Search-UnifiedAuditLog is an exchange online command at the time of this writing. Thus, you need to connect to exchange online. In this function, I am using the switch command. I will follow that structure for the breakdown. Lets first jump in with the function.
I’m glad you came to the breakdown. It means you want to know how the code works. This means you truly care about learning. Thank you. This code repeats itself a few times in different ways. So, I will call out the differences, but not the likes after the first time explaining something. The first section is our Parameters.
Parameters
We have 8 Parameters, and only one of them is mandatory. Firstly, we have the Type parameter. This mandatory validate set allows you to select from a list of commands we will be using in this function.
Deleted
Modified
Created
Moved
Renamed
Downloaded
Uploaded
Synced
Accessed
MalwareDetected
Restored
Locked
UnLocked
Afterward, we have Keep Alive. This allows us to run the command multiple times without signing back into the system. So, if you want to keep your session alive, flip that flag. Next, we have two switches. The first Switch is to pull only items edited in SharePoint itself. The next is for one drive. They are named accordingly. After that, we have a start date and an end date. These values are nullable. Basically, you don’t need them. The outfile is asking for just the name of the file. We are using the “./” to save it wherever you run the command from. Finally, we have the result size. If you want the max number of results, 5000. However, you can make this number smaller.
Begin
In our begin section, we want to test the Exchange Online Management Module. Secondly, we want to validate exchange connectivity. After that, we want to gather the date information for the start and end dates. Let’s take a look at the exchange part first.
The Get-Module command works with PowerShell 5.1. However, I have seen PowerShell flak with this command failing to pull the information. I am going to assume your PowerShell is up to date with your current version.
Afterward, we want to install the exchange online management module if we don’t detect the module. We are using the count to see how many objects are inside our module variable. If it’s 0, it’s time to install. We install it from the PSGallery.
Now, we test exchange connections. We use the Get-PSSession to review the current connections. Next, we test if the connections with the name “ExchangeOnlineInternalSession” is greater than zero. “isconnected” will produce a true or false statement.
If ($isconnected -ne "false") {
try {
Connect-ExchangeOnline
} catch {
Write-Error "Exchange Online Failed. Ending"
end
}
}
After which, we can test with. False, we try to connect. However, if there is an error, we end the script and let the user know. We are not using a credential object to authenticate because MFA should always be a thing.
#Auto Generates Start and Finish dates
if ($Null -eq $StartDate) { $StartDate = ((Get-Date).AddDays(-89)).Date }
if ($Null -eq $EndDate) { $EndDate = (Get-Date).Date }
#Tests if end date is before start date.
if ($EndDate -lt $StartDate) { $StartDate = ((Get-Date).AddDays(-89)).Date }
if ($EndDate -gt (Get-Date).Date) { $EndDate = (Get-Date).Date }
Afterward, we need to get the dates right. If the start date is null, we are going to pull 90 days back. We do this by using the standard. We do the same with the end date. If it’s null, we grab today’s date. Now to prevent errors, we check the start date and end date. The end date can’t be before the start date. This is similar to the end date. The end date can’t be greater than the current date. We use the if statement to resolve this.
Process
We begin the process by looking directly at our “Type” variable by using a switch command. The switch allows us to go through each “Type” and run the commands accordingly. Let’s look at one of the switch processes.
The data that search-unifiedauditlog produces a section called “AuditData”. This section has almost every piece of information you will need. The difference between each “Type” will be the Operations, and session id. The operations target the required logs. This creates the backbone of the Share Point File Audit. The graph below will show which operations I am using. Once you gather the operation information, we need to pull the AuditData. This data will be in JSON format. We start off by looping the records with a for each loop. Then we pull the auditdata and pipe it into convertfrom-json. Next, we create our PS Custom Object. Other than Moved, the output of the other logs contains almost the same information. See the script for the information.
Operation Filters
Deleted
FileDeleted
FileDeletedFirstStageRecycleBin
FileDeletedSecondStageRecycleBin
FileVersionsAllDeleted
FileRecycled
Modified
FileModified
FileModifiedExtended
Moved
FileMoved
Renamed
FileRenamed
Downloaded
FileDownloaded
Uploaded
FileUploaded
Synced
FileSyncDownloadedFull
FileSyncUploadedFull
Accessed
FileAccessed
FileAccessedExtended
MalwareDetected
FileMalwareDetected
Restored
FileRestored
Locked
LockRecord
UnLocked
UnlockRecord
End
Finally, it’s time for the end block. This is where we will present the data we have gathered. Firstly, we need to determine if the SharePoint or Onedrives were flipped or not.
Here we checking if both flags are not checked or if both flags are checked. Then we check if the user gave us a filename. If they did, we export our report to a csv file wherever we are executing the function from. However, if the user didn’t give us a filename, we just dump all the results.
Now, if the user selected either or, we present that information. We present those infos by using a where-object. Like before we ask if the user produced an outfile. Finally, we ask if keep alive was set. If it wasn’t we disconnect from the exchange.
Conclusion
In conclusion, auditing shouldn’t be difficult. We can quickly pull the info we need. I hope you enjoy this powerful little tools.
Recently I was playing with some sql event viewer logs. These logs don’t have properties that we can pull from to make life easier. So, everything has to be parsed through the string. Which isn’t bad, but it’s a challenge to think about. Here is what one of the strings looks like:
Login failed for user 'db_admin'. Reason: Password did not match that for the login provided. [CLIENT: 10.0.80.55]
I wanted three items from this list, the username, the reason, and the IP address. The username is inside the single quotes. The reason goes from the word reason to the [ for client. The IP address is inside the brackets for the client. Lets get started. First lets get the data.
Now we have the data, it’s time to get the username. As I said, the username is found inside the single quotes. So we want to select the string with a pattern that pulls the double single quotes.
This bad boy here grabs everything between the single quotes. The ‘—‘ is the boundaries. the . says to match any character except the terminators. Yeah, we don’t want skynet. Then the X? tells it to match the previous token between 0 and forever times. The select starts off with the first ‘ and then moves to the next character. which is a wild card. Then we search all wildcards forever until the next ‘ with the *? characters.
We select every item in the string that matches that pattern using the -AllMatches. Then we grab the Matches by using the ().Matches. We want those values so we select the value from the matches. ().Matches.Value. This still selects the double single quotes ‘ and we really don’t want this. So we simply remove them by using the -replace command. We replace the ‘ by saying -replace “‘”,””. Looks a little confusing but it works.
Grabbing Text after a word and before a symbol.
The next part is to grab the reason. This is basically grabbing everything after a single word and before something different. The logic is the same as before, but this time we are grabbing it based off a word.
In this instance we are searching for the word Reason:. Once we find that word, we select the first object in front of it using a wild card again. The wild card is a . like before. Then we tell it to continue searching using the * until we reach special character of [. Notice the \ is before the [. The reason for this is because in the world of Regex the bracket, [, is a special character used for searching. Thus this code is saying, start with the word reason: and search everything until you reach the square bracket.
Once we have the pattern we select all the matches like before with the -allmatches. We then select the matches and the values using the ().matches.value commands. From there we want to remove the square bracket and the word reason:. We do that with replace commands to remove the word reason we use -replace (“Reason: “,”) and to remove the extra space and square bracket we us -replace (‘ \[‘,”). Notice once again, the \ before the square bracket.
Pulling an IP address from a string
The next thing we need is the IP address of the log. The IP address is located in the square brackets with the word client inside of it. The key here is not to search those brackets. We want the IP address of any and all strings. We want all the IP addresses and not just one.
This one is much more complex than the last one. The first thing we do is look for is up to three digits side by side. \d means digits. The {1,3} means between 1 and 3. We do this until we reach a . mark. Then we repeat the process again 3 times. We use the () to create a group. Inside that group, we have the \. which is the decimal point followed by the \d{1,3} again. Saying after the decimal point looks for up to three digits again. Finally, we tell the code to do this 3 times with the {3} tag at the end of the group.
Like before we use the -allmatches flag to get all the matches and pipe it out using the ().Matches.value method. But wait! This only pulls the IP address format, not the IP address. This works for an IP address of 512.523.252.1 which we all know is an invalid IP address. To do that we have to dive much deeper into regex. This next part is complex.
Validate an IP address
The above gives an idea of what we want to look for, a starting point, here is the big fish that we need to break down. This code is a bit longer. This code is broken up between whatif structures. Which is pretty cool. It’s going to take some effort to explain it all. So we will take one group at a time with each whatif | statement. Remember each group represented with () is for a single octet. I am going to try my best at explaining this one. I’m not fully sure, but once again, I will try.
Our first what if statement is the 25[0-5]. The maximum number for an octet is 255. In fact, 255.255.255.255 is the broadcast address. In this what-if statement, we are looking at matching the first two characters of an octet as 25 then we are looking for any number after that as a 0 to 5. So, anything like 256 would be ignored.
2[0-4][0-9]|
The next one is for the middle of the 255 range. This tests to see if the range is 2xy. The x is between 0 and 4 and the y is 0 and 9. I’ll be honest, When I changed out the 4 with a 5, the value of 256 was accepted when it shouldn’t have been. There is something special later on on this one.
[01]?[0-9][0-9]?
Now we are matching if the first character is a 1. So, this will cover the 100-199 ranges. If there is a 1 there, if not, then it will cover the 0-99. The ? matches the previous item between 0 and one times. So the previous item is a 1 or a number. This creates the nice 0-199 range.
\.){3}
The \ symbol is our break symbol to break the regex processing on the next item which is our . symbol. Normally . means a wild card. In this case it means a period or decimal point. Then we close the group with our closing parentheses, ). Now we have made a group that determines if an object is between 1 and 255 with a period at the end, we need to do this 3 times. There is multiple ways to do this. We can repeat the code 3 times or we can just say {3}. That’s what we did here.
(25[0-5]|2[0-5][0-9]|[01]?[0-9][0-9]?)
Finally, we repeat the code again. This time without the \. at the end. The reason we do this is that the last octet doesn’t have a period at the end. This matches between 1-255 without the period.
Grabbing Mac Addresses from a string
Another item I pull from logs is mac addresses. Most of the time these are security logs from a firewall. However, it’s important to be able to pull a match address. The big thing between a mac address and an IP address is the mac address requires letters and numbers. They also come in all forms of delimiters. the most common are :, ., –, and a space for the truly evil people. Thus, you have to address each of these items. Here is the code:
([0-9a-fA-F]{2}[: \.-]){5}([0-9a-fA-F]{2})
[0-9a-fA-F]{2}
The first part of the code is looking for the numbers 0 – 9. For example 0F:69:0F:FE:00:01 is the code. The first section is 0F. The 0 – 9 helps us find the 0. The next part a – f helps us find a,b,c,d,e, and f. The A – F helps us find A,B,C,D,E, and F. This way we don’t have to worry about case sensitivity when searching for the logs as some logs don’t capitalize the letters. Finally, we are going to search this two times before our next symbol.
[: \.-]
This next part is the search for the different symbols. Notice we have the common ones in there. The first is the standard colon :. It is followed by a space and then the escape character because the period is a wild card character. Which is then followed by the hyphen – as ipconfig /all gives you hypens. It’s all within the search brackets.
(){5}
We then close up the group with our () marks. This will search for at least one of those that match. We want 5 items in a row for a mac address. Mac addresses contain 6 sections. So, the next code is important to find that 6th. We search for the 5 in the row by the {5} mark.
([0-9a-fA-F]{2})
We repeat the code over again. This way we get that last section of the mac address. This time tho, we don’t have the search for the unique symbols as the last section of a mac address doesn’t have one.
Putting these in a script
If you read my blog often, you know I like functions. Here are two functions you can add to your tool bag.
Get-SHDIPFromString
Function Get-SHDIPFromString{
[cmdletbinding()]
Param (
[string]$String
)
foreach ($string in $string) {
($String | Select-String -Pattern "((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" -AllMatches).Matches.Value
}
}
Example:
PS> Get-SHDIPFromString -String "This message contains 192.168.25.5 as an IP address."
192.168.25.5
Have you ever had troublegetting lockout information for a single user? The DCs have the logs, but getting them can be tough. The core idea is to use the Get-Winevent command to gather the logs needed. Doing this from a remote computer is time-consuming. However, using the Invoke-Command tends to speed things up. Inside this example, I am going to use something called Splatting. Splatting allows you to drop everything a command needs into a command quickly and easily by creating a hash table. Now, not all commands like splatting, so be aware of that. Here is the heart of the code:
The first thing we are doing is creating the parameters that will be splatted. Just like a hashtable, it’s something = something. So the computer name is the Array computername. The computer name inside the Invoke-command can handle arrays. Making this much easier as we want the DCs.
Next is the script block. This is where the invoke-command will be executing everything from. We start off with a Param () block. It will get it’s input from the Argumentlist later. The Argumentlist is the variables we will be dropping into the command. In this case, it will be time. As in this script you can build your time based on how far back you want to search your logs.
Next is the filter table. The Get-winevent command likes hashtables. So we start off with our starttime, basically how far back we are going to go. Then the end time which is the time of execution. The Logname is the type, there is an application, security, system, and more. Since the lockouts are security, we choose security. Finally, we want the ID. For account lockouts, the ID is 4740. There is an online Encyclopedia for windows logs. (Link)
The Get-WinEvent command is followed by the FilterHashtable flag. We dump this into the events variable for sort throu. Now we search each Event in Events. We express this with an E because $event is a system variable and we don’t want to cause any issues. We want the time it was created. The ID, what DC it came from. The SID of the user. The Domain. The username of the user, and finally we want the calling workstation. Once all that is put together we splat it into the command Invoke-Command.
That is the heart, but not the power. This script allows you to call back from the day, hour, and minute back. If you don’t put in a input, it makes it a day for you. It also can target a single user or different computers. Finally it gives you the ability to use different credentials. Lets take a look at the parameters.
param (
[parameter(Helpmessage = "Username of target")][Alias('User', 'Samaccountname')][String[]]$Username,
[parameter(Helpmessage = "Days back")][int]$Day,
[parameter(Helpmessage = "Days back")][int]$Hour,
[parameter(Helpmessage = "Days back")][int]$Minute,
[parameter(Helpmessage = "Computers to target, default is domain controllers.")][Alias('Computer', 'PC')][String[]]$ComputerName = (Get-ADDomain).ReplicaDirectoryServers,
[Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
)
We first are looking for a list of usernames. Notice we don’t have to declare this variable. So, if we just want all the lockouts, we just don’t delcare it. This is done by using the PSBoundParameters.ContainsKey option. So, if the script sees the username is being used, it will follow this command.
if ($PSBoundParameters.ContainsKey('Username')) {
foreach ($user in $Username) {
$Return | Where-Object { $_.Username -like "$user" } | Sort-Object Time | Select-Object Username, Domain, SID, Time, Computer, PSComputerName
}
}
The next parameters are also optional. It is days, hour, and minutes back. So, you can state 1 day, 3 hours, and 2 minutes ago and it will find the times during starting at that moment forwarder. This is good if you plan to use this script as an auditor (what it was built for). How this is achived once again is the use of psboundparameters.containskey.
We first declare the time with the current time/date. Then based on the input, we remove days, hours, or minutes from the current time. If there is no input, we tell time to remove 1 day. Depending on the size of your organization and how many logs you have, this can be useful. The $time will be used in the argumentlist.
Now we have the Computername parameter. If you have differentiating computers than your DCs that handles these logs, you can target them with this command. Otherwise, we grab the dc information with a single line of code.
(Get-ADDomain).ReplicaDirectoryServers
Finally we have the ability to add the credentials. Once you delcare the credentials, we add the credential flag to our splat. To do this we create a hashtable with a single item and add it to the parameter.
if ($PSBoundParameters.ContainsKey('Credential')) {
$parameters += @{Credential = $Credential }
}
Now, lets put all this together in a single script so you can copy it and move along with your day.
I hope you enjoy this little script and I hope it helps you grow your company. If you find any bugs, let me know. Thank you so much and have a great day!
One of the things that have helped me throughout my struggles with Mitel/Shoretel is the NCC logs inside the shoreline data folder. These logs can be hard to read, and very aggravating. Let’s look at a log example.
Below is a table that explains each of the call letters at the beginning of the log.
Call Letters
Event Name
What it does
C-CE
Call Creation Event
Call Initiation
L-CE
Leg Create Event
Follows a C-CE for every call setup. internal transfers (Blind Transfers)
L-IE
Leg Info Event
Follows a C-CE, L-CE. Leg info provides information on other parties in the call.
C-SE
Call State Event
State of call in progress, all parties. This includes RingBack, Offering, established, etc
LSE
Leg State Event
Follows a C-SE to inform the leg state changes.
L-DE
Leg Destroy Event
Call Teardown. The Leg is destroyed.
C-DE
Call Destroy Event
The call is destroyed by the user or system hung up.
G-MST
Media State Event
Media stats for the terminated call leg. Every RTP stream also has a media state event.
Here are examples of what each part will look like.
Trunk call Leg to PSTN
40000001
Internal call Leg
200001B3
Call GUID
00020000-1aae-4f05-9cce-0010491e1b95
PBX Responding to Party
ncc_media_ctl
Sip Info
“sip:3067@10.10.60.2:5441”
Some Info
(s:8757, r:8763, l:0)
Call Quality Info
(j:0,u:0,o:1)
Audio Type
2(ULaw)
Now let’s look at this example below and take it apart from a little. I’m no expert but I have gone through a few hundred of these logs.