General Uninstaller for Intune

General Uninstaller for Intune

This past month I was given a task to Uninstall a few applications in Intune. However, the app’s uninstall feature did not work according to plan. However, a bunch of these worked with the cim methods of uninstall. Which I thought was funny. After writing a bunch of the same code over and over again, I decided to write a General Uninstaller for Intune. This also requires a custom Detection Script.

The General Uninstaller Script

param (
    [string[]]$ProductNames
)
$Products = Get-CimInstance -ClassName win32_Product
foreach ($Product in $ProductNames) {
    if ($null -eq ($Products | where-object {$_.name -like "$Product"})) {
        write-host "Success"
        exit 1212
    } else {
        #Grabs the install Location
        $InstallLocation = ($Products | where-object {$_.name -like "Product"}).InstallLocation

        #Uninstalls the product in question
        $Products | where-object {$_.name -like "Product"} | Invoke-CimMethod -MethodName uninstall
        
        if ($Null -ne $InstallLocation) {
            foreach ($Location in $InstallLocation) {
                if (Test-Path $Location) {
                    Remove-Item -Path $Location -Force -Recurse
                }
            }
        }
        exit 1212
    }
}

Here we have a general uninstaller for Intune. This script allows us to feed the product name as is or we can add wild cards to the name. We start off the script by grabbing the product names from the user. This will be done during the intune setup. When it deploys, the first thing this script does is grab all the applications inside the win32_product. If the application didn’t register in this windows system, then this script is going to be pointless for you.

Once we have the products, we go through each Product Name. We first check to see if the product is on the system. If it isn’t, we output success and exit with a unique exit code. This will be used later. However, if the product is on the machine, we grab the install location. Then we pipe the product into the uninstall method in the cim method command. Finally, we see if the install location exists inside the installed object. Some applications give us this information some don’t. Some give us multiple locations while others don’t.

To work around this, we check if install location property is null. If it isn’t null, then we move on and start a loop. The loop is only because some install locations have more than one location. Then we test if the file path still exists. Sometimes, the applications uninstaller will remove the folder, sometimes, it doesn’t and that’s why we test. If the file location is there still, we remove it with a good old force and recurse. Finally, we exit with the unique exit code.

The General Uninstall Detection Script

$ProductNames = "ProductName","Product2Name"
$Products = Get-CimInstance -ClassName win32_Product
foreach ($Product in $ProductNames) {
    if ($null -ne ($Products | where-object {$_.name -like "$Product"})) {    
        exit 1
    } 
}
write-host "Success"
exit 0

With any custom script installs or uninstalls, a custom detection script is necessary. The first step is to grab the product names. Just like before, it’s a list of strings. So they can do more than one. Then we grab all the products with our cim instance and win32_product. Then we loop through each product name and see if the product exists still. If it does, we exit with a 1. This basically says, I failed! Intune needs a string and an exit code of 0 to be successful. The exit of 1 without the string ends the script and without that string, intune assumes failure. However, if we go through them all, and none trigger the exit, then we are safe to exit with a 0 and the beautiful word success.

Building it out in Intune.

Building the IntuneWin File

The first thing you will need to do is save your script into a folder and then download the WinIntuneApp, aka, Win32 Prep Tool, to package up the powershell script. Unpackage this tool and start up your command prompt. The application will guide you through the process of setting up a intunewin app.

General Uninstaller for Intune
  1. Please specify the source folder: This is the folder that will have your script inside of it. If you wanted to create something more complex, this part would change your way of deployment. Future blog post coming.
  2. Please Specify the setup file: This is going to be the powershell name. General-Uninstall.ps1
  3. please specify the output folder: This is the folder that the intunewin file will be dropped.
  4. Do you want to specify catalog folder (Y/N)? This one is for more advanced packages. We can say no to this option for this setup.

Setting Up Intune for Your Uninstaller

Now we have the IntuneWin file. It’s time to setup the intune Deployment. This is where you will be able to add things like the productname to our General Uninstaller for Intune.

  • Navigate to Endpoint Manager
  • Click Apps
  • Click Windows
  • Click Add
  • Click the Select App Package File.
  • Add the General-Uninstall.IntuneWin file.
  • Click ok
  • Change the Name
  • Click the edit Description and add a detailed description for other users. Make sure to provide instructions on what to do with the detection script.
  • The publisher can be your company or in my case self.
  • The gategory is going to be computer management as it is a general uninstaller.
  • Feel free to add any additional information. Link this blog post if you wish for the information URL.
  • Click Next when finished.

The next screen is programing.

  • Install command:
PowerShell.exe -ExecutionPolicy Bypass -File .\General-Uninstall.ps1 -ProductName "*Product Name*"
  • The Uninstall command can be as simple as a removal file.
  • Device Restart Behavior: Determine behavior based on return codes
  • Return Codes: Remember that unique exit code we had in the script. This is where you would place that code. I have it as 1212 is a success.

The next screen the requirement screen. We can do a lot with this screen, but we don’t need to here.

  • Operating System Architecture:
    • 32
    • 64
  • Minimum Operating System: Windows 10 1607.

Now we need to setup the custom detection.

  • Select User A custom Detection Script
  • Validate your product names to be uninstalled.
  • Upload and click next.
  • Accept the defaults for Dependencies and Supersedences.

The final screen is where you are able to assign the script to people. There are three sections. Required, aviable for enrolled devices and uninstall. This is where you will select who is going to get what.

Testing, Monitoring, and deployment

The assignment area is where you assign the script to who you want. This is very important. Here is where you would want to test the users. Have a test group and apply it first. H

  • Deploy the uninstall app to the test device group.
  • Monitor the Intune deployment status for the app to ensure successful deployment to devices/users.
  • Test if the application is still on a target computer. This can be done with control pannel, powershell, and more options.
  • Redefine and correct any issues and restart the testing.
  • Deploy

What can we learn as a person today?

When was the last time you threw a rock? How about a rock in a lakes? The last time you did, did you notice the ripples? Just like a deployment like this can cause ripples in your company, removing things from your life can cause just as many ripples in yourself. Make sure you are ready to let go of that thing you are holding onto. It’s always a good idea to test it out, or have a support group to help you. Those ripples can do some damage. So be ready to Uninstall parts of your life before you do it.

Additional Reading

AD User Audit with PowerShell

AD User Audit with PowerShell

In the intricate web of modern network management, the security and integrity of user accounts stand paramount. “AD User Audit with PowerShell” isn’t just a technical process; it’s a critical practice for any robust IT infrastructure. Why, you ask? The answer lies in the layers of data and accessibility that each user account holds within your organization’s Active Directory (AD). Whether it’s for compliance, security, or efficient management, auditing user accounts is akin to a health check for your network’s security posture.

Understanding the last login times, password policies, and group memberships doesn’t only highlight potential vulnerabilities; it also paves the way for proactive management and policy enforcement. PowerShell, with its powerful scripting capabilities, emerges as an indispensable tool in this endeavor. It transforms what could be a tedious manual audit into an efficient, automated process.

Why Perform an AD User Audit?

An “AD User Audit with PowerShell” serves multiple purposes:

  1. Security: Identifying inactive accounts or those with outdated permissions reduces the risk of unauthorized access.
  2. Compliance: Regular audits ensure adherence to internal and external regulatory standards.
  3. Operational Efficiency: Understanding user activities and configurations aids in optimizing network management.

The Script:

Import-Module ActiveDirectory

Function Get-PasswordExpiryDate {
    param (
        [DateTime]$PasswordLastSet,
        [System.TimeSpan]$MaxPasswordAge
    )
    return $PasswordLastSet.AddDays($MaxPasswordAge.TotalDays)
}

$MaxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge

Get-ADUser -Filter * -Properties Name, samaccountname, Enabled, LastLogonDate, PasswordLastSet, MemberOf, LockedOut, whenCreated | 
Select-Object Name, samaccountname, Enabled, LastLogonDate, PasswordLastSet, @{Name="PasswordExpiryDate"; Expression={Get-PasswordExpiryDate -PasswordLastSet $_.PasswordLastSet -MaxPasswordAge $MaxPasswordAge}}, @{Name="GroupMembership";Expression={($_.MemberOf | Get-ADGroup | Select-Object -ExpandProperty Name) -join ", "}}, LockedOut, whenCreated | 
Export-Csv -Path "c:\temp\AD_User_Audit_Report.csv" -NoTypeInformation

The Breakdown

The first thing we do in this powershell script is the import module. We are importing the ActiveDirectory. Then we are going to setup a nice little function to help grab the expiry date of the account. The function here will depend on the dates and time as the parameters. The function resolves the time span needed. Next we will grab the data we need. That’s the Name, Samaccountname, enabled, last logon date, password last set, member of, locked out, and finally when created. From there we will use select object. This is where some of the good stuff happens. When we select the pasword expire date, we are going to use the @ name and expression. Lets break that down some.

Special Select

@{Name="PasswordExpiryDate"; Expression={Get-PasswordExpiryDate -PasswordLastSet $_.PasswordLastSet -MaxPasswordAge $MaxPasswordAge}},

Inside the select object command, you can select different properties and give custom properties. Having a name or label is the first part. The Expression can be it’s own command line. Here we have the function from earlier. We are pushing the get-aduser’s password last set and running it through the get-passwordexpirydate. Make sure to wrap the expression inside curly brackets. We can do this same thing with the group membership.

The Group Membership pulls apart the memberof and passes it through the get-adgroup. From there we grab the name of the group. Now since we want to export this into a csv, it would be best to use a join to make it easy. Once we have that information setup. We export it to a csv. Then we are done. This information can then be passed on to the security admin to work with.

Script Deployment and Usage

The execution of this script in your environment is straightforward but requires administrative privileges. It’s designed to run seamlessly, outputting a comprehensive report that encapsulates the health and status of each user account in your Active Directory. This report not only informs your immediate security strategies but also assists in long-term IT planning and policy development.

What can we learn as a person today?

Just as “AD User Audit with PowerShell” ensures the health of an IT infrastructure, journaling serves as a personal audit. It’s a practice that offers self-reflection, tracking progress, and identifying areas for improvement in our personal lives.

Self-Reflection

Journaling is a gateway to the soul, a mirror reflecting our deepest thoughts and feelings. In the quiet moments with our journal, we engage in an intimate conversation with ourselves. This practice allows us to confront our fears, celebrate our successes, and ponder our aspirations. It’s a safe space where we can express emotions without judgment, helping us understand and accept who we are.

Imagine a day filled with stress and uncertainty, where words left unsaid create a storm inside you. In your journal, these unspoken thoughts find a voice. You write about the meeting that didn’t go as planned, the conversation with a friend that left you unsettled. As the words flow, there’s a sense of unburdening, a weight lifting off your shoulders. It’s in this self-reflection that clarity emerges, often bringing peace and a newfound understanding of your emotional landscape.

Tracking Progress

Like a lighthouse guiding ships through a stormy sea, journaling illuminates our path through life’s complexities. It helps us track where we’ve been and where we’re heading, offering insights into our personal evolution. By regularly documenting our experiences, thoughts, and feelings, we create a tangible record of our journey. This practice can reveal patterns in our behavior and responses, helping us recognize both our strengths and areas for growth.

Consider a journal entry from a year ago, describing feelings of apprehension about starting a new venture. Fast forward to today, and your journal tells a different story—one of growth, learning, and newfound confidence. The contrast between then and now is stark, yet it’s a powerful testament to your resilience and adaptability. Reading your past entries, you feel a surge of pride in how far you’ve come, reinforcing your belief in your ability to face future challenges.

Identifying Areas for Improvement

Journaling not only captures our current state but also acts as a compass, pointing out the areas in our life that need attention. It can highlight recurring issues, whether in relationships, work, or personal habits, prompting us to seek solutions or make necessary changes. This process of self-audit through journaling encourages us to be honest with ourselves, to confront uncomfortable truths, and to take actionable steps towards betterment.

Imagine writing about the same problem repeatedly—a strained relationship with a colleague or a persistent feeling of dissatisfaction. Seeing these recurring themes in your journal can be an eye-opener, signaling a need for change. It might inspire you to initiate a difficult conversation, seek external advice, or explore new opportunities. The journal becomes a catalyst for transformation, nudging you towards decisions and actions that align with your true desires and values.

Final Thoughts

In each stroke of the pen, journaling offers a chance for introspection, growth, and healing. It’s our personal audit, tracking our emotional health and guiding us towards a more mindful and fulfilling life. Just as “AD User Audit with PowerShell” ensures the health of our digital environments, journaling safeguards the wellbeing of our inner world.

Additional Resources

Create Bulk Users

Create Bulk Users

Today we are going to go over how to create hundreds of users at once using PowerShell in active Directory. This is great for building a home lab to test things out with. To learn how to build your own AD lab, you can look over this video. Towards the end of this video he shows you how to do the same thing, but, today, I am going to show you a simple way to get unique information. This way you can use PowerShell to Create Bulk Users in your Active Directory.

The Script

$DomainOU = "DC=therandomadmin,dc=com"
$Domain = "therandomadmin.com"
$Users = import-csv C:\temp\ITCompany.csv
$OUs = $users | Group-Object -Property StateFull | Select-Object -ExpandProperty Name
New-ADOrganizationalUnit -Name "Employees" -Path "$DomainOU"
$EmployeePath = "OU=Employees,$($DomainOU)"
foreach ($OU in $OUs) {
    New-ADOrganizationalUnit -Name $OU -Path $EmployeePath
}
foreach ($user in $Users) {
    $Param = @{
        #Name
        GivenName = $User.GivenName
        Surname = $User.Surname
        DisplayName = "$($User.GivenName) $($User.MiddleInitial) $($User.Surname)"
        Name = "$($User.GivenName) $($User.MiddleInitial) $($User.Surname)"
        Description = "$($user.City) - $($User.Color) - $($user.Occupation)"

        #Email and Usernames
        EmailAddress = "$($User.GivenName).$($User.MiddleInitial).$($User.Surname)@$($Domain)"
        UserPrincipalName = "$($User.GivenName).$($User.MiddleInitial).$($User.Surname)@$($Domain)"
        SamAccountName = "$($User.GivenName).$($User.MiddleInitial).$($User.Surname)"

        #Contact Info
        StreetAddress = $user.StreetAddress
        City = $user.City
        State = $user.State
        Country = $user.Country
        HomePhone = $user.TelephoneNumber

        #Company Info
        Company = "DPB"
        Department = $user.Color
        Title = $user.Occupation
        EmployeeID = $user.Number
        EmployeeNumber = $user.NationalID.replace("-",'')
        Division = $user.State

        #Account Data
        Enabled = $true
        ChangePasswordAtLogon = $false
        AccountPassword = ConvertTo-SecureString -String "$($user.Password)@$($user.NationalID)" -AsPlainText -Force
        Path = "OU=$($User.StateFull),$EmployeePath"

        #Command
        ErrorAction = "SilentlyContinue"
        Verbose = $true

    }
    try {
        New-ADUser @Param 
    } catch {
        Write-Error "$($User.GivenName) $($User.MiddleInitial) $($User.Surname)"
    }
}

Bulk User File?

This script is very dependant on a csv file that magically seems to appear. Well, it doesn’t. The first thing we need is to get a CSV of bulk users to create bulk users. To do this, you can navigate to a site called fake name generator. This site allows you to quickly generate user information to use to build your site.

  1. Navigate to the https://www.fakenamegenerator.com/.
  2. Click Order in Bulk
  3. Check the I agree check box
  4. Select Common Sepearted (CSV) and the compression is zip.
  5. Then select your country. I selected American.
    • Note: Some languages will cause issues with AD due to unique characters. If you do select this, make sure to correct for it.
  6. Select your country of choice. I choose the US.
  7. Select the age and gender ranges. You can keep this standard
  8. Then I selected All on the included fields.
  9. Select how many you want and enter email
    • Note: A single OU doesn’t display more than 2000 users. This script creates sub OUs just for this case based on the zodaic signs.
  10. Then verify and place your order.

Once you have the file, we can get started explaining what we are going to do to Create Bulk Users in Active Directory with the Power of PowerShell.

The Breakdown

It’s time to break down this script. The first two lines are the domain information. I’m using therandomadmin.com as a example. The next is the Bulk Users csv. These are the lines you want to change if you want to use this in your own lab. The next line grabs the OUs names. We want the full state names in this case from the csv. Next we will create the Employees OU that will host all of the other OUs.

New-ADOrganizationalUnit -Name "Employees" -Path "$DomainOU"

Now we have the OU built, we will make a path for later. by dropping the Employees and the domain ou into it’s own variable. using this variable, we enter a foreach loop using the OUs. We want to build a new OU for each OU in the OUs.

foreach ($OU in $OUs) {
    New-ADOrganizationalUnit -Name $OU -Path $EmployeePath
}

Next, we will go through the loop of users. In each loop, we want to build a splat. Splatting was covered here in a previous blog. In this splat, we are looking over the New-ADUser commandlet. Lets break it apart.

The Splat

GivenName = $User.GivenName
Surname = $User.Surname
DisplayName = "$($User.GivenName) $($User.MiddleInitial) $($User.Surname)"
Name = "$($User.GivenName) $($User.MiddleInitial) $($User.Surname)"
Description = "$($user.City) - $($User.Color) - $($user. Occupation)"

Using the csv file. We are using the Given name, Surname, and Middle Initial. Using this information, we make the display name, given name, sur name and the name. Then we use the city, color and occupation. The next part is we want to build the usernames.

EmailAddress = "$($User.GivenName).$($User.MiddleInitial).$($User.Surname)@$($Domain)"
UserPrincipalName = "$($User.GivenName).$($User.MiddleInitial).$($User.Surname)@$($Domain)"
SamAccountName = "$($User.GivenName).$($User.MiddleInitial).$($User.Surname)"

Using the same structure as the name, We just add dots and for the email, we just add the domain. Then we will grab the street address, city, state, country, and home phone.

StreetAddress = $user.StreetAddress
City = $user. City
State = $user. State
Country = $user. Country
HomePhone = $user.TelephoneNumber

Next we want to use do the company information. We want the department as the color, the Title will be the occupation, employee id will be the users number, the employee number would be the social and finally the division would be the state.

Company = "The Random Admin"
Department = $user. Color
Title = $user. Occupation
EmployeeID = $user. Number
EmployeeNumber = $user.NationalID.replace("-",'')
Division = $user. State

Now we have company information, we want to make account information. Things like being enabled, password changing, the password and finally the OU. We want to do the Full state name for the OU. This way it matches with the OUs we built before hand.

Enabled = $true
ChangePasswordAtLogon = $false
AccountPassword = ConvertTo-SecureString -String "$($user.Password)@$($user.NationalID)" -AsPlainText -Force
Path = "OU=$($User.StateFull),$EmployeePath"

Finally, we want to push though the command itself. These are the cmdletbinding() flag commands like verbose and error action.

ErrorAction = "SilentlyContinue"
Verbose = $true

Now the splat is done. It’s time to build the try catch with a useful error. By default the Error message is massive. So, making it easier with just the Name is very much more helpful. We will make sure to splat in the new-aduser information.

try {
        New-ADUser @Param 
} catch {
        Write-Error "$($User.GivenName) $($User.MiddleInitial) $($User.Surname)"
}

That’s all for this script. It’s not hard, but it should allow you to create a lab quickly. You can download the CSV here if you wish.

What can we learn as a person today?

Unlike the God’s of old, we are not able to create new people in our lives to meet our needs. Instead, we have to find people. Like we pointed out last week, networking is massive. How we are to other with our networking is extremely important. Without networking, we tend to find ourselves in a hole. Imagine a giant hole in the ground with oiled up smooth metal walls and all you have to get out is a rope that is at the top of the hole. There is a lot that can happen here. The rope can stay there. Someone can throw you the rope.

Throwing the rope

Someone can throw you the rope and walk away. The rope will land in the hole with you. You can try to throw the rope out, but without something to cling to, the rope will just fall back down to you. This is like the man who says to just study for this exam or that exam. He threw you a rope, but hasn’t really done anything else.

Now image if someone secured that rope to something like a car or a rock and threw the other end to you. Now you have something to climb up with. This is the person who has given you resources to look into. For example, I hear you want to get into networking but have no experience. I’m going to say study the network plus exam and then tell you about professor messor on youtube. This is super helpful and most people can climb out of the hole with the rope. However, in this senerio, the wall’s are oiled up. Thus, footing is an issue.

Finally, we have the guy who ties the rope to his car, and throws you the other end. Then backs up with his car pulling you out of the hole. This would be a manager, or a senior member of an IT company taking a new person under their wing and leverging the company to help them learn new things. This is the kind of company, I would want to work with.

Final Thoughts

When you are working with people helping them with their career, some people just need the rope. Some people need the anchor and finally some needs to be pulled out of the hole. A lot of this is due to society and situations. Being aware of these facts can help you network with others better and grow your support team. Being aware of yourself allows you to know who you need as well. Do you need the truck? Do you need an anchor? What is it that we need to get you out of the holes that we find ourselves in? What can we be to others?

Building Parameters for Commands

Building Parameters for Commands

One of my favorite things with powershell is building out splat parameters for commands through the main parameter set. Today we are going to go over how that is done. We are going to do this through the Get-childitem and get-ACL. These are some mighty commands and they can help you find permission gaps quickly and easily. Let us Building Parameters for Commands together.

The Script

Function Get-ACLInfos {
    [cmdletbinding()]
    param (
        [string]$FilePath,
        [switch]$Recurse,
        [switch]$Directory,
        [switch]$File,
        [string]$Filter,
        [string]$Username
    )
    begin {
        if (!(Test-Path -Path $FilePath)) {end}
        $Param = @{
            Path = $FilePath
        }
        if ($Recurse) {$Param | Add-Member -MemberType NoteProperty -Name "Recurse" -Value $true}
        if ($Directory) {$Param | Add-Member -MemberType NoteProperty -Name "Directory" -Value $true}
        if ($File) {$Param | Add-Member -MemberType NoteProperty -Name "File" -Value $true}
        if ($PSBoundParameters.ContainsKey($Filter)) {$Param | Add-Member -MemberType NoteProperty -Name "Filter" -Value "$Filter"}
    }
    process {
        $Items = Get-ChildItem @Param 
        $ACLinfo = foreach ($item in $Items) {
            $ACLs = (Get-Acl -Path $item.FullName).access
            foreach ($ACL in $ACLs) {
                [pscustomobject][ordered]@{
                    Path = $item.FullName
                    FileSystemRight = $ACL.FileSystemRights
                    AccessControlType = $ACL.AccessControlType
                    IdentityReference = $ACL.IdentityReference
                    IsInherited = $ACL.IsInherited
                    InheritanceFlags = $ACL.InheritanceFlags
                    PropagationFlags = $ACL.PropagationFlags
                }
            }
        }
    }
    end {
        if ($PSBoundParameters.ContainsKey('Username')) {
            $ACLinfo | Where-Object {$_.IdentityReference -contains $Username}
        } else {
            $ACLinfo
        }
    }
}

Building Parameters for Commands

This script is simple, it allows you to grab a directory’s ACL recursively. However, how it does it is kind of cool. Get-Childitem has so many different options, but using it within a function can take some of that power away. So, by passing the parameters that we want at the top level allows us to give that power back to the get-childitem. However, this could lead to a lot of if statements. Instead, we are going to build a splat parameter for our commands. I have covered splats in a previous blog post, but I wanted to point them out to express how much power they do have.

The Power of Splat

Splatting is a method in which you can build a parameter set for a command. The amazing part of splatting is the splat is a powershell object. This means you can add to it after the initial splat is started. To start a splat all one has to do is declare a variable with an object attached. The variable is declared with the $ and the object is a simple at symbol followed by curly brackets with an equal sign nestled in the middle. We can use objects anywhere in powershell. A custom PS object is the same way. If you wanted to, you can declare the object and give it an order with [pscustomobject][order]. However, that’s not always the best option as orders cause issue if things are not in that order later down the road.

$Param = @{}

At this point the powershell object is empty. From here we can start building parameters for our object. To do that we are going to use the add-member command. In our example, we are working with true and false statements. Our function parameters are mostly switches. I threw in some strings as well to give you examples of how to build with a string. The first check is to see if we have a recursive. This is simple to do. We ask if the recurse is true. Then we add the member.

$Param = @{
    Path = $FilePath
}
if ($Recurse) {$Param | Add-Member -MemberType NoteProperty -Name "Recurse" -Value $true}

Building the Splat

The add-member starts us down the path of building our splat. The Add-member can give us a lot of different options on which way we want to add things. Here, we need to add the recurse flag to get-childitem. This is a note property inside the command. The best way I see note properties is a name with a value. That value can be null. Here we are adding the “Recurse” name and giving it a value of the boolean true. Thus, when we drop the splat into the command, the command will see a flag of recurse. We do this same method with the rest. Directory is a flag, and so is file.

Unlike the last three, the filter parameter is a string. We are going to use the same method using the note property. We want to give the name as a filter and the value will be our value from our command line. The difference here is we want to place that filter in a string. The next part of the filter is how we test the filter. Instead of doing a simple check to see if the value is true, we need to check the parameters. This is done through the value $PSboundParameters. We want to see which keys the power shell bound parameters are holding. What this means is when you do get-command -something “bob” we want to know if something exists. Then we are going to use that’s something, aka bob.

$Param = @{
            Path = $FilePath
        }
        if ($Recurse) {$Param | Add-Member -MemberType NoteProperty -Name "Recurse" -Value $true}
        if ($Directory) {$Param | Add-Member -MemberType NoteProperty -Name "Directory" -Value $true}
        if ($File) {$Param | Add-Member -MemberType NoteProperty -Name "File" -Value $true}
        if ($PSBoundParameters.ContainsKey($Filter)) {$Param | Add-Member -MemberType NoteProperty -Name "Filter" -Value "$Filter"}

Finishing the Splat

The next step is to use the splat. Which is very easy to do. To use a splat, instead of using the dollar sign, all you need to do is use the at symbol and the parameter. The Command should not have any other flags set as it all lives inside the splat. Building a parameter splat is super easy and makes life easier in the long run.

$Items = Get-ChildItem @Param 

What can we learn as a person from splatting

As we go through our lives, we are a representation of a splat. We start off with a few things and over time we add and remove aspects from ourselves. Others add and remove aspects of us as well. Growing up in school, teacher pours so much of their selves into their students. As we get older, we have to do the same for ourselves. We have to add the note properties to our lives, or we will always stay the same. Today you add-member -membertype noteproperty “Splatting” -value “dang it’s cool” to yourself. Unlike a computer though, we have to practice to bring it close to ourselves. We have to conceptualize the idea.

As you go through life, you have to depend on your own splat. Not enjoying that splat, means you have to work on it. It takes action to do so. The thing that stops most people from taking that action is fear of the unknown or fear of dealing with the pain. As someone who has been working on himself for many years now, I can safely say, it’s ok not to be ok. It’s ok not to enjoy parts of your splat, but overall, your splat is who you are. So go out into this world and put your @ on things. Change up that splat if you don’t like parts of it. Just enjoy being yourself.

Additional Resources

Read Radius Logs With PowerShell

Read Radius Logs With PowerShell

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.

The Script and Breakdown

$NPSLogs = Get-content -Path "C:\Logs\Radius\IN2308.log" | Select-Object -Last 6
foreach ($NPSLog in $NPSLogs) {
    [pscustomobject][ordered]@{
        TimeStamp = ([xml]$NPSLog).event.Timestamp.'#text'
        ComputerName = ([xml]$NPSLog).event.'Computer-Name'.'#text'
        Sources = ([xml]$NPSLog).event.'Event-Source'.'#text'
        Username = ([xml]$NPSLog).event.'User-Name'.'#text'
        CalledStationId = ([xml]$NPSLog).event.'Called-Station-Id'.'#text'
        CallingStationId = ([xml]$NPSLog).event.'Calling-Station-Id'.'#text'
        NasPortType = ([xml]$NPSLog).event.'NAS-Port-Type'.'#text'
        NasIdentifier = ([xml]$NPSLog).event.'NAS-Identifier'.'#text'
        NasPort = ([xml]$NPSLog).event.'NAS-Port'.'#text'
        ClientIPAddress = ([xml]$NPSLog).event.'Client-IP-Address'.'#text'
        ClientVendor = ([xml]$NPSLog).event.'Client-Vendor'.'#text'
        ClientFriendlyName = ([xml]$NPSLog).event.'Client-Friendly-Name'.'#text'
        ProxyPolicyName = ([xml]$NPSLog).event.'Proxy-Policy-Name'.'#text'
        ProviderType = ([xml]$NPSLog).event.'Provider-Type'.'#text'
        SamAccountName = ([xml]$NPSLog).event.'SAM-Account-Name'.'#text'
        FQUsername = ([xml]$NPSLog).event.'Fully-Qualifed-User-Name'.'#text'
        AuthType = ([xml]$NPSLog).event.'Authentication-Type'.'#text'
        Class = ([xml]$NPSLog).event.Class.'#text'
        PacketType = ([xml]$NPSLog).event.'Packet-Type'.'#text'
        ReasonCode = ([xml]$NPSLog).event.'Reason-Code'.'#text'
        TunnelClientEndpt = ([xml]$NPSLog).event.'Tunnel-Client-Endpt'.'#text'
    }
}

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.