In the Field – IP and Mac Info

In the Field – IP and Mac Info

Today in the field, we needed to find all of the device’s IP and Mac Info for each network adapter. This was a large undertaking. We were looking for a special network adapter that didn’t show up in our normal reports from our RMM. However, our RMM could push out PowerShell scripts and return the network adapters for us. The information we needed was the network adapter index number. Then we needed the mac address and IP address. Finally, we needed the name of the network device. The stage is set.

Network Adapters

Firstly, we need to grab the network adapters. We are using the Get-NetAdapter command. This command lists all the current network adapters on the device in question.

$Adapters = Get-NetAdapter

The net adapter gives us the name of the adapter, the mac address, and the index we are looking for. Since we placed the net adapters in a workable variable, we can loop this variable. While looping, we are targeting the interface index.

Net IP Address

Secondly, the next piece we need is the IP information from each network adapter. We do this with the Get-NetIPAddress command. This command grabs the IP address of each network adapter, along with the interface Index. This means we can marry the mac address to the IP address. This command produces both the IPv4 and IPv6 address.

$IP = Get-NetIPAddress 

This command produces a large list of useful information. The information we want is the index, ipv4 and ipv6 addresses. Since we have both pieces of the pie, it’s time to marry the two.

IP and Mac Info

As a result of having the above commands information, we can marry these two together. We start with a foreach loop of the adapters.

$Return = foreach ($Adapter in $Adapters) {
    #Do Something
}

We are looping the adapters because it’s the physical side of things. Plus, the IP information directly links to the network adapter. We place the foreach loop information into a variable. This allows us to filter later. Next, is the PS custom object. A PS custom object is the object we are after.

$Return = foreach ($Adapter in $Adapters) {
    [pscustomobject][ordered]@{
        #Information
    }
}

It’s within this object that magic happens. The current adapter will provide the name, interface description, mac address, index, link speed, and status. From there we will grab the IPv5 address by using where-objects. First, we search for the index of the IP command for the adapter index. Then we search for the address family to be IPv4. We repeat this process for IPv6. This is what the loop looks like.

$Return = foreach ($Adapter in $Adapters) {
    [pscustomobject][ordered]@{
        Name           = $Adapter.Name
        Description    = $Adapter.InterfaceDescription
        MacAddress     = $adapter.MacAddress
        InterfaceIndex = $Adapter.Interfaceindex
        LinkSpeed      = $Adapter.LinkSpeed
        Status         = $Adapter.Status
        IPV4           = ($IP | where-object { $_.InterfaceIndex -eq $Adapter.Interfaceindex } | where-object { $_.addressfamily -like "IPv4" }).IPaddress
        IPV6           = ($IP | where-object { $_.InterfaceIndex -eq $Adapter.Interfaceindex } | where-object { $_.addressfamily -like "IPv6" }).IPaddress
    }
}

After that, the loop finishes and we display a return. We sort the object by the link status, this way we see the active ones first.

$Return | Sort-Object -Property Status

The Script – IP and Mac Info


$Adapters = Get-NetAdapter
$IP = Get-NetIPAddress 
$Return = foreach ($Adapter in $Adapters) {
    [pscustomobject][ordered]@{
        Name           = $Adapter.Name
        Description    = $Adapter.InterfaceDescription
        MacAddress     = $adapter.MacAddress
        InterfaceIndex = $Adapter.Interfaceindex
        LinkSpeed      = $Adapter.LinkSpeed
        Status         = $Adapter.Status
        IPV4           = ($IP | where-object { $_.InterfaceIndex -eq $Adapter.Interfaceindex } | where-object { $_.addressfamily -like "IPv4" }).IPaddress
        IPV6           = ($IP | where-object { $_.InterfaceIndex -eq $Adapter.Interfaceindex } | where-object { $_.addressfamily -like "IPv6" }).IPaddress
    }
}
$Return | Sort-Object -Property Status

Conclusion

Even though this case was a fringe case, it still shows the power that Powershell can provide. We were able to grab the IP and Mac Info for each network adapter. From there we were able to generate the report needed. Pull all of the Mac addresses and IP addresses needed to complete the project. Overall, this was a success for us. I hope you are able to use some if not all of this code somewhere.

Azure AD Hardening- Revoke disabled sign-in tokens

Azure AD Hardening- Revoke disabled sign-in tokens

As part of my efforts to harden clients’ azure accounts, We need to remove/refresh all the sign-in tokens from the disabled account. Why? That’s very simple, If you don’t, then any signed-in device still has access. For example, if I terminate a user, I block their sign-in in office 365. The block will take effect within 24 hours. This is why we want to Revoke Disabled Sign-in tokens.

The user can steal data if you don’t remove/refresh the sign-in tokens. By removing/refreshing the sign-in tokens, the outlook can no longer authenticate back with office 365. This means no more new emails. Everything on the computer is still a free game. The user can decode the PST files. I revoke/refresh the tokens with a single command from the Azure AD PowerShell module.

Revoke Disabled Sign-in

Get-AzureADUser | where-object {$_.AccountEnabled -eq $False} | foreach-object {Revoke-AzureADUserAllRefreshToken -ObjectId $_.ObjectId}

The Breakdown

It’s time to break down the “Revoke Disabled Sign-in” script. The first step is to grab all the users from the azure ad. We do this with the Get-AzureADUser command. Next, we parse that information through a where-object. We want to filter out the accountEnabled by the value of false. Now that we have all the disabled users we start a foreach-object loop. Inside our loop, we want to trigger the Revoke-AzureADUserAllRefreshToken command using the Object ID. We are grabbing the previous command’s output using the $_ object. The ObjectID is the object we are pulling out.

Conclusion

At the end of the day, this code snippet is a catch-all for standard processes. Whenever you terminate an employee, it’s always a good idea to revoke the sign-in tokens.

I hope this helps. Please let me know if you have any questions.

Microsoft Safety Scanner Powershell One Liner

Microsoft Safety Scanner Powershell One Liner

A client called in and told me a line of information that made me concerned about security. I ran a webroot scan and wanted to give another level of the scan. I am partial to the Microsoft Safety Scanner. It runs well connectwise backstage. You can read more about the safety scanner here. So, lets look at this oneliner.

if (!(Test-Path "c:\Temp")) { New-Item -Path "c:\" -Name Temp -ItemType Directory }; invoke-webrequest -Uri https://go.microsoft.com/fwlink/?LinkId=212732 -OutFile c:\temp\mss.exe -UseBasicParsing; c:\temp\mss.exe /Q /F:Y

The first part of this little script is to test and create the folder that will hold our file. We are doing this by using test-path. Then if the file doesn’t exist, aka !. Then we create it with the new-item.

if (!(Test-Path "c:\Temp")) { New-Item -Path "c:\" -Name Temp -ItemType Directory }

The next part is we are going to download the Microsoft security scanner from Microsoft directly. The link is the direct download. We use invoke-webrequest to download the file. The -outfile flag is where we will download the file at and its name. In this case, we are going to name it something simple. Mss.exe inside our temp folder. We use the -usebasicparsing because most machines only have PowerShell 5.

invoke-webrequest -Uri https://go.microsoft.com/fwlink/?LinkId=212732 -OutFile c:\temp\mss.exe -UseBasicParsing

Then we run the command needed. We start the command with the path. C:\temp\mss.exe. We want it to be quiet and we want to force it. So we use the /Q to quiet, and /F:Y to force.

c:\temp\mss.exe /Q /F:Y

The system will not prompt for any kind of approval. It will run and delete what it needs to delete. This is a simple, deploy and walk away one-liner. So, add it to your deployment scripts and enjoy scanning with a Microsoft safety scanner.

Handle with PowerShell

Handle with PowerShell

Lets talk about Handle. Handle is an amazing program that allows you to see which program has access over a folder or file. This is a sysintel tool. Working with handle inside your powershell script is not a native thing. The first thing you will want to do is download handle.

We first create the folder we want handle to be downloaded in. In this case, the c:\temp folder will work. Notice we check first to see if it exists with the test path. We will continue this trend so we don’t have to go through the download and creation process repetitively.

if (!(Test-Path "c:\Temp")) { New-Item -Path "c:\" -Name Temp -ItemType Directory }

Now we test to see if handle has already been downloaded before. If not, we download it. We are going to be downloading the handled application from http://live.sysinternals.com/handle.exe All of the Sysinternals tools are on this website and you can programmatically download them at any time. We are going to save the handle.exe in our c:\temp folder we created a few seconds ago.

if (!(Test-Path "C:\temp\handle.exe")) {
        Invoke-WebRequest -Uri "http://live.sysinternals.com/handle.exe" -OutFile "c:\temp\handle.exe"  -UseBasicParsing -DisableKeepAlive
    } 

Now we have handle, it’s time to get a handle on handle inside PowerShell. As command prompt program it needs to be called from the command prompt. We want to capture the output. Thus, using something like start-process is out of the question and here is why. Start process starts another processes. It does not keep the process in the current window. Thus you can not capture that data without doing some PowerShell magic which may or may not work. So, what we do instead is use the cmd.exe itself. We will use the /c flag and then the path to the handle software.

$ProcessHandles = cmd.exe /c C:\temp\handle.exe -a -u "$FilePath" -accepteula

Let’s break this down a little more. We are starting the handle application with an -a. The A is dumping all the handle information. This is a ton of information. The -u shows the owning user name when searching for handles. So we are grabbing all the information and the user information. We want to do this because it gives us the programs as well. Then we give it the path of the folder we want. So we basically give it a target. Now we are pulling all the handle information from a target folder with the user name/process name. The final handle flag is -accepteula. This basically makes it more automated. We call the handle using the cmd.exe /c. This brings the command output into our terminal which we can capture by placing into the $processHandles. Bam, now we have a bunch of confusing string information. The next step is to parse this string. Here is what the string looks like:

Nthandle v4.22 - Handle viewer
Copyright (C) 1997-2019 Mark Russinovich
Sysinternals - www.sysinternals.com

WINWORD.EXE        pid: 14248  type: File          ACTIVEDIRECTORY\bolding  A34: C:\temp\Change Control.docx

Now we need to handle the handle strings. So we search each string for the file name or file path with a simple where-object. This should create an array of information.

$Handles = $ProcessHandles | where-object { $_ -like "*$FilePath*" }

In this case, we only have one, but we want to make sure it doesn’t break if there is more than one. So we start a foreach loop. We loop through each handle in our handles. Each handle loops like this:

WINWORD.EXE        pid: 14248  type: File          ACTIVEDIRECTORY\bolding  A34: C:\temp\Change Control.docx

They are split apart by spaces. So, what we are going to do is use the split features. We are going to then search each line for an *.exe as most programs are .exe at the end of the day. We could expand upon this, but we will leave it here at this level. Once we have the .exe we want to remove that .exe with the replace command. Here is what the code will look like so far.

foreach ($Handle in $handles) {
        $Process = ($handle.split(' ') | where-object { $_ -like "*.exe" }) -replace '.exe', ''
}

Notice how we pipe one command into another and then wrap it with the replace. Simple one-line power right there. From here we need to test if the $process is empty. We do this because if the file in question isn’t locked down, we don’t want to error out. So a simple, if null is not equal to process, is set. The goal is to push these items into a smart system that will kill the process. However, there is one item I have discovered over the years doing this that tends to get killed by going down this route and that’s explorer.exe. I have killed it more than once. This is why I place an exclusion for explorer.exe. To do this we just check if the name matches with another if statement. So here is what the code looks like so far for this loop.

$Tasks = foreach ($Handle in $handles) {
        $Process = ($handle.split(' ') | where-object { $_ -like "*.exe" }) -replace '.exe', ''
        if ($Null -ne $process) {
            if ($Process -notlike "explorer") {
                $Process
            }
        }
    }

Now, here is the fun part. We can kill these tasks from the script itself. All we have to do is loop it through and stop each process with a stop-process. I placed a kill switch in the parameters just for this. So, if the kill switch is true, then we loop through each task killing it. If not, then we just display the processes. It’s that simple. Here is what that code looks like:

if ($kill) {
        foreach ($Task in $tasks) {
            Stop-Process -name $Task -Force
        }
    } else {
        $Tasks
    }

It’s that time, let’s put it all together and make the script.

The Script

function Set-SHDLockedFileProcess {
    param (
        [String]$FilePath,
        [switch]$kill
    )
    if (!(Test-Path "c:\Temp")) { New-Item -Path "c:\" -Name Temp -ItemType Directory }
    if (!(Test-Path "C:\temp\handle.exe")) {
        Invoke-WebRequest -Uri "http://live.sysinternals.com/handle.exe" -OutFile "c:\temp\handle.exe"  -UseBasicParsing -DisableKeepAlive
    } 
    $ProcessHandles = cmd.exe /c C:\temp\handle.exe -a -u "$FilePath" -accepteula
    $Handles = $ProcessHandles | where-object { $_ -like "*$FilePath*" }
    $Tasks = foreach ($Handle in $handles) {
        $Process = ($handle.split(' ') | where-object { $_ -like "*.exe" }) -replace '.exe', ''
        if ($Null -ne $process) {
            if ($Process -notlike "explorer") {
                $Process
            }
        }
    }
    if ($kill) {
        foreach ($Task in $tasks) {
            Stop-Process -name $Task -Force
        }
    } else {
        $Tasks
    }
}
Finding Old Snapshots with PowerShell

Finding Old Snapshots with PowerShell

Do you need to find Old Snapshots on a hyper-v server? It’s super easy. So, today we will go through how to get some basic information that allows us to make judgment calls.

The Script – Find Old Snapshots

$Date = (Get-Date).AddDays(-7)
$Vms = Get-VM | where-object { $_.state -like "Running" } 
$Return = foreach ($VM in $Vms) {
    $SnapShots = $VM | Get-VMSnapshot
    foreach ($SnapShot in $SnapShots) {
        if ($snapshot.creationTime -lt $date) {
            [pscustomobject]@{
                SnapShotName         = $SnapShot.name
                SnapShotCreationDate = $SnapShot.CreationTime
                VituralMachine       = $SnapShot.VmName
                Host                 = $SnapShot.ComputerName
            }
        }
    }
}
$Return

The Breakdown

The first part of the script is getting the age requirements. In this case, we want to know anything older than 7 days. So we use the Get-Date command. We add -7 days and this will give us the date to compare by.

$Date = (Get-Date).AddDays(-7)

In this case, we only want the running machines. The reason I want running machines is that the powered-off machines might be in a decommissioning process or for other reasons. So we look at the state of each VM to see if it’s “Running”. We do this with a where-object.

$Vms = Get-VM | where-object { $_.state -like "Running" } 

Now we have the running VMs to work with, we want to get each one’s snapshot. We want to compare each snapshot to see if it’s older than the date. The information we want is the name of the snapshot, the snapshots creation date, the vm, and the hostname. So we start for each loop. Inside the look, we ask with an if statement the creation time is less than the date we created earlier. Then from there we create a PS custom object and pull out the information we want.

$Return = foreach ($VM in $Vms) {
    $SnapShots = $VM | Get-VMSnapshot
    foreach ($SnapShot in $SnapShots) {
        if ($snapshot.creationTime -lt $date) {
            [pscustomobject]@{
                SnapShotName         = $SnapShot.name
                SnapShotCreationDate = $SnapShot.CreationTime
                VituralMachine       = $SnapShot.VmName
                Host                 = $SnapShot.ComputerName
            }
        }
    }
}

Then finally we output the $return value. We can export this to a CSV and drop it into a file share. I personally do this with a nextcloud instance. You can read more about that here. Another option is to email the report using a Microsoft Graph API or an SMTP email system. Finally, if you have confidence in your choice, you can delete the VMs.

Conclusion

Running this script and combining it with the file drop and a few other pieces of automation changed how I worked with multiple clients. This was a good cleanup process and saved many of my clients’ much-needed storage space. Let me know how you use this code for your systems.