Test Microsoft Service Connections

Test Microsoft Service Connections

This past week I have had multiple terminals up with different clients on different terminals connected to different Microsoft services. I quickly realized that I needed to know if I was already connected or not to each Microsoft service. I knew that Get-PPSession was a key to this, but what surprised me was azure and msols didn’t have a PPSession to review. I use 4 different connections on a daily basis. The Msol, Exchange Online, Compliance, and Azure AD. Here is how you can quickly test each service to see if it’s connected.

Msol Service

With Msol we want to test a command to see if it works. We output that command to null. The command I found to be the quickest for this is Get-MsolDomain. Once we run the command we check to see how successful that command was with $?. I like to put things into arrays for future use. So, I dropped it into a $Results and then present those display those results.

Get-MsolDomain -Erroraction SilentlyContinue | out-null
$Results = $?
$Results

Exchange Online

With Exchange online, we can use the Get-PSSession. We search the connection uri for outlook.office365. We also look at the state to be opened. We then ask if how many sessions there are. if it’s greater than 0, then we are good to go. Believe it or not, this is a one-liner.

(Get-PSSession | Where-object { ($_.ConnectionURI -like "*outlook.office365.com*") -and ($_.State -like "Opened")}).count -gt 0

Compliance

With Compliance, it’s similar to the exchange online. We are using the Get-PSSession again. This time we are looking for the word compliance and the state open as well. Once again, this can be a one-liner.

(Get-PSSession | where-object { ($_.ConnectionUri -like "*compliance*") -and ($_.State -like "Opened") } ).count -gt 0 

Azure AD

Finally, we come to the Azure AD, Just like the Msol, we have to check it using a command. I have seen people use the Get-AzureADTenantDetail commandlet. So we will use that commandlet and pipe it into out-null. Then we confirm if it worked with the $? command.

Get-AzureADTenantDetail -ErrorAction SilentlyContinue | out-null
$Results = $?
$Results

The Function

Let’s combine it together into a function.

function Test-SHDo365ServiceConnection {
    <#
    .SYNOPSIS
        Tests to see if you are connected to verious of services. 
    .DESCRIPTION
        Tests to see if you are connected to Microsoft Online Services, Exhcange Online, Complience Center, and Azure AD.
    .PARAMETER MsolService
        [switch] - Connects to Microsoft Online Services
    .PARAMETER ExchangeOnline
        [switch] - Connects to Exchange Online
    .PARAMETER Complience
        [switch] - Connects to complience services
    .PARAMETER AzureAD
        [switch] - Connects to azure AD
    .EXAMPLE
        PS> Test-SHDo365ServiceConnection -MsolService 
        Gives a trur or False statement on weither connected or not. 
    .OUTPUTS
        [pscustomobject]
    .NOTES
        Author: David Bolding

    .LINK
        https://github.com/rndadhdman/PS_Super_Helpdesk
    #>
    [cmdletbinding()]
    param (
        [switch]$MsolService,
        [switch]$ExchangeOnline,
        [Switch]$Complience,
        [switch]$AzureAD
    )
    if ($MsolService) {
        Get-MsolDomain -Erroraction SilentlyContinue | out-null; $Results = $?
        [pscustomobject]@{
            Service   = "MsolService"
            Connected = $Results
        }
    }
    if ($ExchangeOnline) {
        $Results = (Get-PSSession | Where-object { ($_.ConnectionUri -like "*outlook.office365.com*") -and ($_.State -like "Opened")}).count -gt 0
        [pscustomobject]@{
            Service   = "ExchangeOnline"
            Connected = $Results
        }
    }
    if ($Complience) {
        $Results = (Get-PSSession | where-object { ($_.ConnectionUri -like "*compliance*") -and ($_.State -like "Opened") } ).count -gt 0 
        [pscustomobject]@{
            Service   = "Complience"
            Connected = $Results
        }
    }
    if ($AzureAD) {
        Get-AzureADTenantDetail -ErrorAction SilentlyContinue | out-null; $Results = $?
        [pscustomobject]@{
            Service   = "AzureAD"
            Connected = $Results
        }
    }
} #Review
Playing with Logs – Regex

Playing with Logs – Regex

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.

$test = invoke-command -ComputerName servername -ScriptBlock {Get-WinEvent -FilterHashTable @{logname='application';providername='MSSQLSERVER';Keywords='4503599627370496'}}

Grabing Everything between double single qoutes.

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.

($t.message | Select-String -Pattern "'.*?'" -AllMatches).Matches.Value -replace "'",""

‘.*?’

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.

Reason =  (($t.Message | Select-String -Pattern "Reason:.*\[" -AllMatches).Matches.Value -replace ' \[','') -replace 'Reason: ',''

“Reason:.*\[“

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.

IPAddress = ($t.message |  Select-String -Pattern "\d{1,3}(\.\d{1,3}){3}" -AllMatches).Matches.Value

\d{1,3}(\.\d{1,3}){3}

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.

^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-5][0-9]|[01]?[0-9][0-9]?)$

25[0-5]|

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  

Get-SHDMacFromString

function Get-SHDMacFromString {
    [cmdletbinding()]
    Param (
        [string[]]$String
    )
    foreach ($string in $String) {
        ($String | Select-String -Pattern '(?<mac>([0-9a-fA-F]{2}[: \.-]){5}([0-9a-fA-F]{2}))' -AllMatches).matches.value
    }
}

Example:

PS> Get-SHDMacFromString -String "Physical Address. . . . . . . . . : 11-35-AF-FE-11-A1"
11-35-AF-FE-11-A1
Resolve a Site name to Geo Location

Resolve a Site name to Geo Location

With everything that happened with Facebook yesterday, I began to wonder where does my query goes when I type in facebook.com. So, I did a few things and found out. The first thing I did was resolve the name facebook.com to an IP address, or group of IP addresses in this case with the command resolve-dnsname.

Resolve-DnsName -Name facebook.com

Then from there, I used the site, ip-api.com to pull the location information of the IP address. This awesome little site gives you city, state, country, zip codes, and even the ISP information of an IP address.

$Info = Invoke-RestMethod -Method Get -URI "http://ip-api.com/json/$IP"

That’s the base of the code that we will explore. It’s very straightforward, but I want to clean it up some. I want to make a Get GEO IP information and a Resolve DNSname to Geo IP. I want it to all work together even if there is multiple IP addresses and hosts names. So, lets start off with the scripts and break them down. This will contain two functions for what we are wanting.

Get-SHDGeoIP

function Get-SHDGeoIP {
    [cmdletbinding()]
    param (
        [parameter(Mandatory = $true)][ipaddress[]]$IPAddress,
        [switch]$Complete
    )
    foreach ($IP in $IPAddress) {
        
        $Info = Invoke-RestMethod -Method Get -URI "http://ip-api.com/json/$IP"
        if ($Complete) {
            $Info
        }
        else {
            [pscustomobject]@{
                IPAddress = $info.Query
                City      = $Info.city
                State     = $Info.regionName
                Country   = $Info.country
                ISP       = $Info.isp
            }
        }
    }
}

This script is going to pull the geo information for us. We start off with the parameters. We are testing the parameters to see if the IP address is an valid IP address. We do that with [ipaddress]. This tests for both IPv4 and IPv6. We tell it to be a array of IPaddresses with the [] inside of it. [ipaddress[]]. Just for cleaner fun, I have a switch for a complete information dump. This way

Since this is an array of IP addresses, we will start a foreach loop for each IP address in the array. We start the foreach loop by grabbing the IP information. If the user selected complete, we just dump the information we gathered to the user. if they didn’t select complete, we create a custom object with the IP address, city, state, country and ISP information.

Resolve-SHDDNSNameToGeoIP

Function Resolve-SHDDNSNameToGeoIP {
    [cmdletbinding()]
    param (
        [parameter(Mandatory = $true)][string[]]$Hostname,
        [switch]$Complete
    )
    foreach ($Name in $Hostname) {
        if ($Complete) {
            Get-SHDGeoIP -IPAddress (Resolve-DnsName -Name $Name).IPAddress -Complete
        }
        else { 
            Get-SHDGeoIP -IPAddress (Resolve-DnsName -Name $Name).IPAddress
        }
    }
}

The next function uses the previous function and combines it with Resolve-DnsName. We start off with a list of strings for our hostname parameter and our complete parameter. We start our loop like before of the host names. Then we use the Get-SHDGeoIP -IPAddress command with the Resolve-DnsName -Name and the link name. We then select the IP addresses which is an array. We place that array inside the Get-SHDGeoIP and bam, we have our information. Converting a hostname like Facebook.com to IP information.

With these two little scripts, you will be able to find quick information about a website and where it is being hosted. For example, this site is hosted in new jersey. I personally didn’t know that.

Let me know if you use this and how.

Log Public IP Changes

Log Public IP Changes

This little script logs public IP address changes. The original design was to email out. I’ll make sure to point out when and where to put that code. So the idea is simple. It checks to see if it’s public IP address has changed using an invoke-webrequest. If it has changed, it checks to see if a log has been made. If it has been made, it updates the log, if it hasn’t been made, it creates the log. Then when the IP address changes back, it backs up the logs and stops. Lets take a look at the script.

The Script

Param (
    [string]$IPAddress = "Your IP Address"
)  
$IP = (invoke-webrequest -Uri "http://ifconfig.me/ip" -UseBasicParsing).content
If (!(Test-Path C:\temp)) { New-Item -Path "c:\temp" -ItemType Directory }
if (!($IP -like "$IPAddress")) {
    if (!(Test-Path c:\temp\IPTrigger.log )) {

#Email Code Goes Here

        $Datetime = (Get-Date).ToString("yyyy-MM-dd_HH-mm")
        "Datetime,IP" > c:\temp\IPTrigger.log
        "$datetime,$IP" >> c:\temp\IPTrigger.log 
    }
    else {
        $Datetime = (Get-Date).ToString("yyyy-MM-dd_HH-mm")
        "$datetime,$IP" >> c:\temp\IPTrigger.log 
    }
}
else {
    if (Test-Path c:\temp\IPTrigger.log) {
        $Datetime = (Get-Date).ToString("yyyy-MM-dd_HH-mm")
        Copy-Item -Path c:\temp\IPTrigger.log -Destination "c:\temp\IPTrigger_$($datetime).log.bak"
        Remove-Item -Path  c:\temp\IPTrigger.log

#Email Code goes here with attachment of the log bak. 

    }
}

The Breakdown

We grab the IP address from the Paramters. This way you can have the script called Public-IPChecker -IPAddress Something from your task scheduler. After we get what the IP Address should be we get what It is with the Invoke-webrequest command.

$IP = (invoke-webrequest -Uri "http://ifconfig.me/ip" -UseBasicParsing).content

There are a hand full of sites that grabs the IP address for you. I personally like Ifconfig.me because it places the IP address as the content. Thus no sorting through the HTML to get the IP address.

Next we ask if it’s the IP address. Then we ask if the log has been made.

if (!($IP -like "$IPAddress")) {
    if (!(Test-Path c:\temp\IPTrigger.log )) {}
}

If it doesn’t exist, we create it by getting the DateTime into a file friendly format. and Piping that information into the log file along with the current IP address.

$Datetime = (Get-Date).ToString("yyyy-MM-dd_HH-mm")
"Datetime,IP" > c:\temp\IPTrigger.log
"$datetime,$IP" >> c:\temp\IPTrigger.log 

If it does exist, we just update the log file using the same code but this time we remove the “Datetime,IP” part.

$Datetime = (Get-Date).ToString("yyyy-MM-dd_HH-mm")
"$datetime,$IP" >> c:\temp\IPTrigger.log 

If the IP addresses do match then we test to see if the log file is there. If it is there we create a bak of the log file and remove the old one. This is when you can send an email saying it’s back up. If it doesn’t exist, we do nothing.

if (Test-Path c:\temp\IPTrigger.log) {
        $Datetime = (Get-Date).ToString("yyyy-MM-dd_HH-mm")
        Copy-Item -Path c:\temp\IPTrigger.log -Destination "c:\temp\IPTrigger_$($datetime).log.bak"
        Remove-Item -Path  c:\temp\IPTrigger.log
}

That’s the full script in a nutshell.

Quser to PSObject

Quser to PSObject

I have been using Quser for years and I was wondering how to parse the data out to Powershell. The best way to do this is to convert the output into a csv and then convert it from the csv to a psobject. Let us get started. First we output the quser to a variable.

$Users = Quser /server:"$($env:COMPUTERNAME)" 2> $null

In this example, we are going to grab the local computer’s logged-in information. Here is what the output looks like:

PS C:\Users\david> Quser /server:"$($env:COMPUTERNAME)" 2> $null
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 david                 console            11  Active       1:05  9/5/2021 9:29 PM
 susan                                    12  Disc         1:05  9/6/2021 11:14 AM

Next, we replace the first (^) > with nothing. It doesn’t look like much but it adds the spacing needed.

$Users = $Users -Replace '^>',''

Here is the output.

 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 david                 console            11  Active       1:08  9/5/2021 9:29 PM
 susan                                    12  Disc         1:08  9/6/2021 11:14 AM

Now we will replace the tabs with commas.

$Users = $Users -Replace '\s{2,}',','
USERNAME,SESSIONNAME,ID,STATE,IDLE TIME,LOGON TIME
 david,console,11,Active,1:08,9/5/2021 9:29 PM
 susan,12,Disc,1:08,9/6/2021 11:14 AM

From here we want to go through each line and remove the last item if it has too many spaces. This happens when you run this command on a server. You will need to go through each line by line with a foreach loop.

$users = foreach ($User in $Users) {
    if ($User.Split(',').count -eq 5) {
        Write-Output ($User -replace '(^[^,]+)','$1,')
    } else {
        Write-Output $User
    }
}

Splitting the user by the comma, we need only 4 not 5. So we remove the first of the last object. If it is 4, we do nothing special and just reintroduce the original $user.

USERNAME,SESSIONNAME,ID,STATE,IDLE TIME,LOGON TIME
 david,console,11,Active,1:08,9/5/2021 9:29 PM
 susan,,12,Disc,1:08,9/6/2021 11:14 AM

Now we take that $user csv and convert it to a psobject using convertfrom-csv.

$users = $users | ConvertFrom-Csv
USERNAME    : david
SESSIONNAME : console
ID          : 11
STATE       : Active
IDLE TIME   : 1:08
LOGON TIME  : 9/5/2021 9:29 PM

USERNAME    : susan
SESSIONNAME :
ID          : 12
STATE       : Disc
IDLE TIME   : .
LOGON TIME  : 9/6/2021 11:14 AM

Next, we need to do some house cleaning. Sometimes the Idle time will show up as a dot. We don’t want that. We want it to show up as null. We do this by finding all the dot Idle times and setting that idle time to null with a foreach-object.

$users | where-object { $_."IDLE TIME" -like "." } | ForEach-Object { $_."IDLE TIME" = $null }
USERNAME    : david
SESSIONNAME : console
ID          : 11
STATE       : Active
IDLE TIME   : 1:08
LOGON TIME  : 9/5/2021 9:29 PM

USERNAME    : susan
SESSIONNAME :
ID          : 12
STATE       : Disc
IDLE TIME   :
LOGON TIME  : 9/6/2021 11:14 AM

Finally, we output the information by just putting the $users. That’s it yall. Lets put it together.

The Script

$Users = Quser /server:"$($env:COMPUTERNAME)" 2> $null
$Users = $Users -Replace '^>',''
$Users = $Users -Replace '\s{2,}',','
$users = foreach ($User in $Users) {
    if ($User.Split(',').count -eq 5) {
        Write-Output ($User -replace '(^[^,]+)','$1,')
    } else {
        Write-Output $User
    }
} 
$users = $users | ConvertFrom-Csv
$users | where-object { $_."IDLE TIME" -like "." } | ForEach-Object { $_."IDLE TIME" = $null }
$Users

Another Way

Another way to do this is to grab a process from the machine that everyone logged in would be using. Something like explorer. We do this with the get-ciminstance and the wmi object win32_process.

Get-ciminstance -ClassName win32_process -filter 'name ="explorer.exe"'
ProcessId Name         HandleCount WorkingSetSize VirtualSize
--------- ----         ----------- -------------- -----------
10576     explorer.exe 2805        142168064      2204137693184
18900     explorer.exe 2404        185618432      2204239441920

Then we pipe this information into Invoke-CimMethod and use the GetOwner method to grab the owner information.

Get-ciminstance -ClassName win32_process -filter 'name ="explorer.exe"' | Invoke-CimMethod -MethodName GetOwner

Domain          ReturnValue User  PSComputerName
------          ----------- ----  --------------
DESKTOP-XXXX0LB           0 david
                          2    

From here we grab the user information using a .user.

(Get-ciminstance -ClassName win32_process -filter 'name ="explorer.exe"' | Invoke-CimMethod -MethodName GetOwner).user