Emotet is a really nasty, destructive threat causing huge problems for organizations around the world.

It’s fast-moving, constantly changing, and uses multiple advanced techniques to get through your defenses – which means it needs the very best defenses to stop it.

Check out this of Emotet malware and how to stop it with Sophos. Use these resources below to educate your customers on Emotet and how Sophos helps stop it.

Get path and name of process by port

This needs to run as an administrator to get paths for all processes

This one liner gets the path of the process that is using local port 61119

([System.Diagnostics.Process]::GetProcessById((Get-NetTCPConnection -localport 61119).owningprocess)).path

This will get you a list of process identifiers (PID) sorted by local port

Get-NetTCPConnection | select localport, owningprocess | sort localport

Finally get all the ports in use by a process

Get-NetTCPConnection -owningprocess 61980 | select localport

Know a port and wonder what other ports the process is using?

Get-NetTCPConnection -owningprocess([System.Diagnostics.Process]::GetProcessById((Get-NetTCPConnection -localport 61119).owningprocess).id) | select localport

Find something on your HD

I was trying to search for something on a large file system, but between circular references in symbolic links creating errors with long paths etc it was harder than I thought it should be.

Here is some quick powershell to do a recursive, case insensative search while skipping symbolic links, but it does pick up hidden files and folders and ones with strange characters in the names.

function Recurse($path) {
# $path.tolower()

$files = Get-ChildItem -literal $path -file -force
foreach ($file in $files) {
if ($file.fullname.ToLower().contains($searchstring)) {
# write-host $file.fullname -foregroundcolor "magenta"
$FoundArray.add( $file.fullname )
$folders = Get-ChildItem -literal $path -directory -force
foreach ($folder in $folders) {
# $folder.fullname
if ($folder.fullname.ToLower().contains($searchstring)) {
# write-host $folder.fullname -foregroundcolor "magenta"
$FoundArray.add( $folder.fullname )
if ( (get-item -literal $folder.fullname -force).Attributes.ToString().Contains("ReparsePoint") -eq $false) {
Recurse $folder.fullname
$FoundArray = New-Object System.Collections.ArrayList
recurse "C:\Users"

Get logon and logoff times for user

I know there are some issues with this and it should depend on some lag in replication times. To be really accurate you should be auditing these events and running this on your DC’s but for a quick and dirty option to get the times for a users on a specific server you can run the following script.

I got the base of this somewhere and had to alter it to do what i needed. I lost the initial location  in the process.  If you find it let me know so I can provide credit

function Get-LogonHistory {
$logons = Get-EventLog Security -AsBaseObject -InstanceId 4624,4647 |
Where-Object { ($_.InstanceId -eq 4647) `
-or (($_.InstanceId -eq 4624) -and ($_.Message -match "Logon Type:\s+2")) `
-or (($_.InstanceId -eq 4624) -and ($_.Message -match "Logon Type:\s+10")) }
$poweroffs = Get-EventLog System -AsBaseObject -InstanceId 41
$events = $logons + $poweroffs | Sort-Object TimeGenerated

if ($events) {
foreach($event in $events) {
# Parse logon data from the Event.
if ($event.InstanceId -eq 4624) {
# A user logged on.
$action = 'logon'

$event.Message -match "Logon Type:\s+(\d+)" | Out-Null
$logonTypeNum = $matches[1]

# Determine logon type.
if ($logonTypeNum -eq 2) {
$logonType = 'console'
} elseif ($logonTypeNum -eq 10) {
$logonType = 'remote'
} else {
$logonType = 'other'

# Determine user.
if ($event.message -match "New Logon:\s*Security ID:\s*.*\s*Account Name:\s*(\w+)") {
$user = $matches[1]
} else {
$index = $event.index
Write-Warning "Unable to parse Security log Event. Malformed entry? Index: $index"

} elseif ($event.InstanceId -eq 4647) {
# A user logged off.
$action = 'logoff'
$logonType = $null

# Determine user.
if ($event.message -match "Subject:\s*Security ID:\s*.*\s*Account Name:\s*(\w+)") {
$user = $matches[1]
} else {
$index = $event.index
Write-Warning "Unable to parse Security log Event. Malformed entry? Index: $index"
} elseif ($event.InstanceId -eq 41) {
# The computer crashed.
$action = 'logoff'
$logonType = $null
$user = '*'

# As long as we managed to parse the Event, print output.
if ($user=$username) {
$timeStamp = Get-Date $event.TimeGenerated
$output = New-Object -Type PSCustomObject
Add-Member -MemberType NoteProperty -Name 'UserName' -Value $user -InputObject $output
Add-Member -MemberType NoteProperty -Name 'ComputerName' -Value $env:computername -InputObject $output
Add-Member -MemberType NoteProperty -Name 'Action' -Value $action -InputObject $output
Add-Member -MemberType NoteProperty -Name 'LogonType' -Value $event.ReplacementStrings[8] -InputObject $output
Add-Member -MemberType NoteProperty -Name 'TimeStamp' -Value $timeStamp -InputObject $output
Add-Member -MemberType NoteProperty -Name 'IP' -Value $event.ReplacementStrings[18] -InputObject $output
Write-Output $output
$fulloutput += $output
} else {
Write-Host "No recent logon/logoff events."

$fulloutput | Export-Csv -Path .\logonlog.csv -Encoding ascii -NoTypeInformation
$fulloutput= @()


Ransomware – Better get used to it


The reality is that Ransomware is here to stay. There is simply too much money involved for it to go away in the near term. Antivirus and technology companies do their best to keep up safe but new variants arrive almost everyday.

This means that you need to be extra vigilant. Be careful and don’t open documents from people you don’t know. Be very careful when downloading files when you are not sure who the author is. Keep your operating system, browser, and antivirus up to date on your computer and phone (yes your smartphone too). Don’t turn off UAC or run as an administrator just because it’s a little inconvenient from time to time. Most importantly have a good backup that has version control or one that you keep offline. The first thing Ransomware typically wants to do is delete all your backups.


One of the latest attacks just asks you to turn on macros.  For reference a document will never look like this just because your macros are off.


If you are not sure don’t open it. If you are worried it might be legitimate ask professional first. 

It’s much easier to tell you that it’s OK to open than to recover if it wasn’t. 

The Support Scam : Needed “A Special Hell”

By now I expect by now everyone at one time or another received a call from technical support telling them there is something wrong with their computer.

They used to say they were from Microsoft openly but that ended when some of them got busted http://www.bbc.com/news/technology-26818745

Sadly the money to be made is significantly better than the penalties if you get caught.  Perhaps they will go to “The Special Hell”


I have been getting these calls off and on for a long time and have talked to them in the past. I have always been interested in knowing what they are trying to do from a technical level. The first time I tried the scammer talked to me like I had never touched a computer for about half an hour. Eventually I ran out of patience and told him that I knew what a windows key was and could he just tell me what program he wanted me to run. (trying to short cut the 5 minutes of instructions on how to get a run prompt). He said

“Ok what you need to run is F*@! you – Click”

So there was half an hour wasted I guess I will have to play dumb longer next time. Mostly I don’t have time so I just hang up and my wife just tells them she has a Mac and they leave her alone.

This morning I was between client and had some time when the call came in and I decided to give it another go. The call came in from 232-555-6985 this area code is from Sierra Leone, and I have talked to my telephone provider who says there is nothing that can be done because of the caller ID spoofing that they use. Personally I believe that this kind of spoofing needs to be fixed globally as part of the infrastructure but for now we just have to live with the fact that we can’t just block calls based on the caller id number.

Have you ever called for tech support? do you recall how hard you had to work to get someone to listed to you and get your issue fixed? The first thing to remember is that Microsoft and or tech support does not just call home computers users.

Never run commands on your computer that have been provided to you by a stranger who calls you

The first thing they asked me to do as to run

ASSOC – This has been in windows systems for a very long time. It lets you see what programs run when a file with given extension is clicked on. Notes  Used without parameters, assoc displays a list of all the current file name extension associations. Near the bottom of the output is the following line. 


You are then told that CLSID is Computer Level Secirity IDentifier and he will then read you the code and you can confirm it. This way you know the call is legitimate and not one of the scams going on.

A CLSID is a globally unique identifier that identifies a COM class object, and is NOT unique and is the SAME on every computer (or at least close enough for the scammers)

next you are asked to run

eventvwr.exe – This shows you all the logs of what is going on in a windows system. If you are like me you spend a lot of time here but most people never look. The only thing you really need to know is that it is completely normal to have yellow and red icons here. I was asked if I knew what the events were and I said I did (I was getting tired of this game) I picked the top event in the and told him what it was.

Log Name: System
Source: Microsoft-Windows-Kernel-Processor-Power
Event ID: 37
Task Category: (7)
Level: Warning

The speed of processor 3 in group 0 is being limited by system firmware.

I told him that this was just power saving features of my laptop and was immediately corrected. I was told that this error message is proof that hackers are inside my computer trying to do bad things and it needs to be fixed right away.

The next command I was asked to run was

inf hacking files (actually I had to quickly look this up before I ran it, I really enjoyed putting the scammer on hold for 2 minutes to “take another call”)

What this command actually does is open your c:\windows\inf folder (the hacking files does nothing) this is a normal system folder that handles softeare and drivers (If you care details here)

I was asked to identify all the files there, and look for anything that ended PNF. I know these files are normal and part of windows installer but again I was told this is proof that my computer had been compromised.

At this point in the call he was clearly fishing for me to say something specific because for the next 15 minutes he went over these details again and again until I agreed that he was legitimate and my computer was infected by hackers. Saying things like “If you say so I believe you” was not good enough. When I finally said I was infected we moved on


Now he asked me to run

iexplore support.me  – this will connect you to https://secure.logmeinrescue.com/Customer/Code.asp (Please don’t associate this scam with logmein hold your thoughts about these remote control companies until the end)

This is where the scammer is attempting to gain access to my computer (something I am not about to let happen) I was then asked for the 6 digit computer security code that came with my computer. Then I was scolded for not keeping such critical system documentation. (no emmy here but at least he tried to sell it as he read from the page he had) But wait fortunately I am in luck as he can generate me a new code as long as I promise not to loose it this time.

If I enter this code in the logmein website it would let then gain access to my computer.  Fortunately logmein appears to take this kind of abuse very seriously and you will notice a link just below where you key in you code “We take security seriously, Report Abuse”  so while I told him the download was running I was reporting his code in the background. This much of the call took about 40 minutes and now it was my turn to take up some of their time, they have decided they have a sucker on the line and don’t want to let me go.

I complained that the logmein application kept saying connecting but never finished. I was provided 2 different codes to try and downloaded the application multiple times , and then I was transferred and I knew they really didn’t want to let me off the phone.  For reference the entire call to this point was very noisy there were clearly dozens of calls going on in the background and the person I was talking to clearly had a significant challenge with english.  All of a sudden things are very different, the call is quiet and I am speaking (and hearing clearly) with someone with a slight Indian-Brtish accent? He is not reading from a script but is here to fix any technical issues to get me connected. I am provided another access code and I tell him I have another call and will be right back. This time I use my cell to call the 1800 number on the logmein abuse and someone answers right away, he explains they are working hard to prevent this abuse of their system and can report the access codes I have been given so they can be traced back and the account closed and blocked. He is very polite and thanks me for helping them prevent this abuse of their system.

Quickly I get back on the phone with the scammer and tell him this just is not working. Now he directs me to a new site.


Now this site work differently as I have to install the software and give them a code (not the other way around) and there is no clear way to report abuse and nothing I can provide to showmypc.com to trace back (at least without letting the scammer connect to my pc) we are close to an hour now and I have no good answer for the scammer as to why I can’t provide the code so I hangup. I do manage to find a support chat and contact showmypc.com

John: Hi
John: How may I help you?
Scott: Just wondering what I can do to report fake support from india trying to use your software to to take control of my computer
John: We are already aware of that and we are trying to add features to avoid such issues.
Scott: is there anything I can get from them to get for you to catch them?
John: We have a lot of things but, they are using the free version which can’t be limited however, we will put a warning on our homepage as well.
Scott: I think that should be a top priority you are assisting in the abuse of a lot of people right now
John: Yes we will do that ASAP.

Shortly after I receive an email from showmypc.com

Hi Scott.

We saw your recent chat, just wanted to let you we currently have an active warning on our home page.
Any time a user tries to download the software, he see a big red active warning message, and also after they try to use the are shown a warning.

And in addition, we have an case filed with FBI for investigating this.

If you have something concrete to add we will be happy to take information from you to block. We constantly block users who we detect are trying to abuse our system.

Now what they say is true but I you don’t see the warning until after you go to click on showmypc

8-29-2014 12-53-56 PM8-29-2014 12-54-15 PM







They say they constantly block users but at the same time told me in the chat it couldn’t be limited.

I have been through this with showmypc.com and although they say they are all over it it doesn’t feel that way to me. make your own decision.

I think remote support tools are a good thing, even if I feel that showmypc.com doesn’t take this as seriously as I would like them to.

Bottom line don’t waste your time, hangup and move on.


Can’t change password – The password does not meet the password policy requirements.

Ever have a user call and complain that they can’t change their password?

They get an error message “The password does not meet the password policy requirements.” but they promise that the new password is complex?

Any you try and get the same thing?

Could be a minimum password age policy issue…



This powershell will lookup your domain password policy and match it against a user and the last password change.


I am posting this mostly because I had a hard time retrieving the  domain policy parts.

$Domain.maxPwdAge.Value kept returning an object of System.__ComObject

It took a lot of searching to eventually find a way to get the actual value out.

I am sorry I didn’t save the links to give credit… At the same time anyone who can explain this to all of us I would sure like to understand why $lngMaxPwdAge = $Domain.ConvertLargeIntegerToInt64($MPA) works and $mpa.value does not…

$MPA = $Domain.maxPwdAge.Value

<em id="__mceDel">$lngMaxPwdAge = $Domain.ConvertLargeIntegerToInt64($MPA)</em>



Either way here is the script that will tell you the policy and when a password was last changed.

It will show an alert if the password is under the min or over the max password age.


$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')

While (!$result)
$UserName = Read-Host 'Username to check : '
if (!$UserName)
Write-Host "No Username Entered"
$searcher.Filter = "(&amp;(objectClass=User)(samAccountName=" + $username + "))"
$result = $searcher.Findone()
# get domain password policy (max pw age)
$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Domain = [ADSI]"LDAP://$D"
$MPA = $Domain.maxPwdAge.Value
$MinPA = $Domain.minPwdAge.Value
# Convert to Int64 ticks (100-nanosecond intervals).
$lngMaxPwdAge = $Domain.ConvertLargeIntegerToInt64($MPA)
$lngMinPwdAge = $Domain.ConvertLargeIntegerToInt64($MinPA)
$MinPwdLength = $Domain.minPwdLength
$PwdHistory = $Domain.pwdHistoryLength

# Convert to days.
$MaxPwdAge = -$lngMaxPwdAge/(600000000 * 1440)
$MinPwdAge = -$lngMinPwdAge/(600000000 * 1440)

$lngPwdLastSet =$result.Properties.pwdlastset
$pwdLastSet = [datetime]::FromFileTime($lngPwdLastSet[0])

Write-Host $result.Path
Write-Host $result.Properties.cn " " $result.Properties.userprincipalname
Write-Host "Password Last Set : " $pwdLastSet
Write-Host "Max Password Age : " $MaxPwdAge
Write-Host "Min Password Age : " $MinPwdAge
Write-Host "Password History : " $PwdHistory
Write-Host "Min Password Length : " $MinPwdLength
if ($pwdLastSet -ge (Get-Date).AddDays(-$MinPwdAge)){Write-Host -ForegroundColor Red "Password can not be changed - Min Age"}
if ($pwdLastSet -ge (Get-Date).AddDays($MaxPwdAge)){Write-Host -ForegroundColor Red "Password Expired"}



A location that is unavailable?? but it’s right there!!

I was asked to look into the error a client was receiving.  The user connected to a domain joined windows 2008 R2 server with an account that had local admin rights. Mapped a drive (M) to a file share browsed to a file and tried to copy it to the local D drive. He got the typical UAC prompts and then the following error message.


“M:\ refers to a location that is unavailable. It could be on a hard drive on this computer, or on a network. Check to make sure that the disk is properly inserted, or that you are connected to the Internet or your network, and then try again. If it still cannot be located, the information might have been moved to a different location.”



Some quick searches picked up lots of strange suggestions more than a few suggesting you just disable UAC and move on.  (Don’t do that) If you want to understand what actually happening read on, if not skip to the end….

When you logon to a system with UAC you get a split token at logon. You may have the option to elevate to admin rights but typically you are working away happily with normal user rights.

The secret here is to think of this split token almost like to separate sessions in this case, and here is the proof.

Logon and map a drive, then open a command prompt and do a “net use” to see the mapped drive.


Now at the same time run a command prompt as an administrator and do the same thing. Note Administrator in the title bar.

No mapped drive….

You start to copy a file using explorer.exe (running in your normal user process) you right-click on a file or folder in the mapped drive and select copy. (so far so good)

Now you go to a UAC protected location like the root of a drive, in this case D:\ and right-click paste. (everything still working just fine)

Now you get a UAC prompt and approve the move to the Administrator token.

The copy now tries to complete moving the file or folder you previously copied (m:\whatever.txt) but of course now that you are under the admin token M:\ doesn’t exist and the copy fails.



1) Create a folder and copy there. If it’s not UNC protected this is not an issue

2) Use a UNC instead of a mapped drive – Because this does not use a drive letter the copy does not fail.

3) Map the drive under your admin token as well and the copy will work.



I have found this issue happens even when logged on via RDP as a local administrator in this instance, but aside from that  this is a good explanation of whats going on. Regardless of the specifics you can use the above technique to see if this is causing your specific problem.

This could be an issue in a number of scenarios wherever you have a combination of UAC  and mapped drives so don’t worry too much about the OS version etc just try the test and see for yourself if you have this kind of error.




Report on SIDHistory

This script will use PowerShell to check your domain for any users or groups that have a SID history, and will report the SID History, current SID and username to c:\UserSid.csv

You can use the current domain or specify something specific.


$strFilter = "(&amp;(|(objectCategory=User)(objectCategory=Group))(sidHistory=*))"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher

$objSearcher.SearchRoot = $objDomain
#$objSearcher.SearchRoot = "LDAP://dc=domainname, dc=com" #or manually override domain here
$objSearcher.PageSize = 100
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"

"OldSID,CurrentSid,OldName,NewName" | Out-File c:\UserSid.csv

$colProplist = "name" , "objectsid", "sidhistory"
foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)| Out-Null}

$colResults = $objSearcher.FindAll()

foreach ($objResult in $colResults)
$UserSid = New-Object System.Security.Principal.SecurityIdentifier($objResult.Properties.objectsid[0], 0)
foreach ($sidhistory in $objResult.Properties.sidhistory)
$OldUserSid = New-Object System.Security.Principal.SecurityIdentifier($sidhistory, 0)

$sid = new-object System.Security.Principal.SecurityIdentifier($usersid)
$user = $sid.Translate([System.Security.Principal.NTAccount])
if ($user)
$outstring = $OldUserSid.Value + "," + $UserSid.Value + "," + $objResult.Properties.name + "," + $user.value
$outstring = $OldUserSid.Value + "," + $UserSid.Value + "," + $objResult.Properties.name + ",Not Found"
$outstring | Out-File -append c:\UserSid.csv


List all the groups a user is a member of … Thank-You PowerShell

Let me start by saying that if you still cling to vbscript (as I did for so long) I have to say come to the dark side we have cookies. Seriously things are so much better once you get it figured out.

For whatever reason I started working with arrays in this script and learned a few cool things.

Remember how hard it was to actually remove an item from an array?
and this is done using the value of the array member so you don’t even need to know it’s address!

recall having to write whole loops to bubble sort your array? another one liner now.


And perhaps my favorite in this script, how to loop through an array and see if it contains a value, again a single line.

$array -contains "value"

The other thing I do a lot in this script is work with AD objects. Search for a user find all the groups they are a member of then bind each group and check its members etc etc. Lots of good examples of how to get a handle on an AD object. took me a long time to get it right but once you do it’s easy.

$Object= [ADSI]("LDAP://"+ $x)

where $x is the distinguished name of the object…. don’t know the DN? use the searcher

$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
$searcher.Filter = "(&amp;(objectClass=User)(samAccountName=$SAMAccountName))"
$result = $searcher.Findone()

The only trick now that you have the name is actually getting the AD object to work with.


Starting to enjoy PowerShell yet?

# for more info s@blackops.ca
#The script will prompt you for a user logon name (no domain)
#If you click cancel or don’t enter a name the script will exit
#The script will continue to prompt you until it gets a valid username.
#The script will then load all the groups the user is a member of and go through all the nested groups reporting the hierarchy and noting any groups the use is a member of more than once.
#At the end it will output a single list sorted by group name to make it easier to find a specific group.

# This is the core function it takes an array of AD groups via distinguished name and a depth field to help with spacing in the output. For each group that is a member of a previous group it will call itself with the new array and depth +1
function GroupEnnumerate ([System.Collections.ArrayList]$InputArray, [int]$Depth)
for ($i=1;$i -le $Depth; $i++) {$spacer=$spacer + "`t"}
#the two above lines add a tab char foe each level deep to keep the output formatted

While ($InputArray.count -gt 0) #repent until the input array is empty
#The next line is important to make sure we have not already processed a group.
#This prevents an endless loop if a group is a member of itself either directly or indirectly
if (!($expandedgrouparray -contains $InputArray[0]))
$GroupName = $InputArray[0]
$expandedgrouparray.add($InputArray[0])|Out-Null #add the new group to the array of processed groups
$Group = [ADSI]("LDAP://"+ $groupname) #lookup the group in AD
$expandedgrouparraynames.add($group.name.tostring())|Out-Null #add the group friendly name to an array for the output at the end
Write-Host $spacer($Group.Name) #display the group in the output
$TempGroupArray = New-Object System.Collections.ArrayList $null #blank and init the temp array of sub groups
foreach ($x in $group.memberof) # for each sub-group do the following
$member = [ADSI]("LDAP://"+ $x) #lookup the memberof  in AD

If ($member.SchemaClassName -eq "group") #we don't care if the member is not a group, just in case...
$TempGroupArray.add($x)|Out-Null #add the sub-group to a temp array to be used when calling the function for the next loop
if ($TempGroupArray.Count -gt 0) #if we found any sub-groups start checking them
[Array]::Sort([array]$TempGroupArray) # sort the array so he output is easier to read
GroupEnnumerate $TempGroupArray ($Depth+1) # call the function again with the temparray if sub-groups and add another tab to the output
$tempGroupArray=$null #blank out the temp array
$InputArray.remove($InputArray[0]) #as we process each group in the input array we need to remove it
#If we have already processed the group once then just report and remove it from the input array
write-Host -ForegroundColor Red $spacer([ADSI]("LDAP://"+ ($InputArray[0]))).name "- Already a member"

$StartGroupArray = New-Object System.Collections.ArrayList
$ExpandedGroupArray = New-Object System.Collections.ArrayList
$ExpandedGroupArrayNames = New-Object System.Collections.ArrayList
#I create the arrarys as above so I can use the .add .remove etc

$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
$PromptMessage="Please enter the user logon name"

#the following do loop continues until you don't enter anything or you enter a good username
$SAMAccountName = Read-Host $PromptMessage

if ($SAMAccountName) #if there was ANY input in the box
$searcher.Filter = "(&amp;(objectClass=User)(samAccountName=$SAMAccountName))"
$result = $searcher.Findone() # search AD for the requested user
if ($result)
# if we find a user in ID then get the AD object to work with later
# if we don't find a user update the message prompt so the user knows something happened
$PromptMessage= "User " + $samaccountname + " not found - Please enter the user logon name"
exit #if there was not ANY input in the box quit the whole script
while (!$user) # until we have a valid user object in AD keep pestering the operator

$groups = $user.memberof # get a list of all the groups that this user is a member of
foreach($group in $groups)
#for each group that they are a member of add the group DN to the array for processing
$StartGroupArray = $startGroupArray + $group
Write-Host "User: " $user.displayName " " $user.sAMAccountName " is a member of:" #this is the header of the output
[Array]::Sort([array]$startGroupArray)#fort the array so the output is easier to read
GroupEnnumerate $startGroupArray 1 #initiate the top-level group check
Write-Host # space in the output
Write-Host "Complete list sorted by group name" #second output header
$ExpandedGroupArraynames | Sort-Object #sorted list of all the groups processed that the user is a member of.
Write-Host # space in the output
Write-Host "Script Complete"