PowerShell: Finding Those Pesky Service Accounts

In most Windows environments I’ve worked in, there is rarely any good documentation, especially documentation that tells you were service accounts are being used. This always presents a problem when you have to change the account’s password, or have to change the account all together. Getting tired of things breaking when this happens, I finally wrote a PowerShell script to go out and find these accounts.

This script connects to each machine listed in a text file or a specific Active Directory OU, and goes through each account you want to find. This is also useful to find old accounts to get rid of, or accounts that are in places they shouldn’t be. It then dumps the results in a CSV in the specified directory. It checks local Administrators group, MS SQL server if installed, Scheduled Tasks, and Services. The user account it is run under needs to have administrator rights to the machines being tested.

This requires PowerShell 4.0 or higher. Enjoy!

#Location of the Output file:
$OutputDir = "c:\temp\"
#Location of the user list (comma or line separated):
$UserListLocation = "c:\temp\userlist.txt"

#Location of the machine list (comma or line separated):
#$MachineList = c:\temp\serverlist.txt

#OR get servers by AD OU
If (!(Get-Module ActiveDirectory)) {
    Import-Module ActiveDirectory
$MachineList = Get-ADComputer -SearchBase 'OU=Servers,dc=test,dc=domain,dc=com' -Filter '*' | Select -Exp Name


If (Test-Path $UserListLocation){
    $UserList = Get-Content $UserListLocation
    Write-Host "User list was not found. Exiting."

#Function to grab the local admins from a machine
function Get-LocalAdmins{
    $group = Get-WmiObject win32_group -ComputerName $computerName -Filter "LocalAccount=True AND SID='S-1-5-32-544'"
    $query = "GroupComponent = `"Win32_Group.Domain='$($group.domain)'`,Name='$($group.name)'`""
    $list = Get-WmiObject win32_groupuser -ComputerName $computerName -Filter $query
    $list.PartComponent | % {$_.substring($_.lastindexof("Domain=") + 7).replace("`",Name=`"","\")}
#Function to get users from SQL
function Get-SQLAdmins{
    foreach($SQLSvr in $SQLServers){
        $MySQL = New-Object Microsoft.SqlServer.Management.Smo.Server $SQLSvr
        $SQLLogins = $MySQL.Logins
        $SysAdmins += foreach($SQLUser in $SQLLogins){
            foreach($role in $SQLUser.ListMembers()){
                $SQLUser | Select-Object @{label = "SQLServer"; Expression = {$SQLSvr}}, @{label = "CurrentDate"; Expression = {(Get-Date).ToString("yyyy-MM-dd")}}, Name, LoginType, CreateDate, DateLastModified
$Machine = ""
$IsInAdmins = ""
$IsInSQL = ""
$IsInTasks = ""
$Services = ""
$OutputFileName = ""
$ErrorOutputFileName = ""
#Go through and test each machine listed
ForEach($User in $UserList){
    $OutputFileName = $OutputDir + "Results-" + $User + ".csv"
    $ErrorOutputFileName = $OutputDir + "Results-" + $User + "-ERRORS.txt"
    #Clean up the directory if already used
    If (Test-Path $OutputFileName){
           Remove-Item $OutputFileName
    If (Test-Path $ErrorOutputFileName){
           Remove-Item $ErrorOutputFileName
    ForEach($Machine in $MachineList){
        $MachineAdmin = "\\" + $Machine + "\C$"
        If (Test-Connection $Machine -quiet -count 1){
            If (Test-Path $MachineAdmin -ErrorAction SilentlyContinue){
                Write-Host "$($Machine) connected."
                #See if specified user is in the SQL server on the machine (if present)
                $object = Get-WmiObject win32_service -ComputerName $Machine | Where {($_.name -like "MSSQL$*" -or $_.name -like "MSSQLSERVER" -or $_.name -like "SQL Server (*") -and $_.name -notlike "*helper*" -and $_.name -notlike "*Launcher*"}
                if ($object){
                    $InstanceInfo = $object | Select Name
                    ForEach ($Instance in $InstanceInfo){
                        $SQLInstance = $Machine + "\" + $instInfo
                        Get-SQLAdmins $SQLInstance
                        If ($SysAdmins.Name -like "*$User*"){
                            $IsInSQL = $True
                    $IsInSQL = ""
                #See if specified user is in the local admin group
                $UserList = Get-LocalAdmins $Machine
                If ($UserList -like "*$User*") {
                    $IsInAdmins = $True
                    $IsInAdmins = ""
                #Find services that uses specified user for login
                $query = "SELECT Name FROM Win32_Service WHERE StartName LIKE '%$User%'"
                $Services = Get-WmiObject -ComputerName $Machine -query $query
                If ($Services){
                    $Services = $Services -replace 'Win32_Service.Name=',""
                    $Services = $Services -replace '"',""
                #Find any scheduled tasks running as the user
                $TaskInfo = Invoke-Command -ScriptBlock {schtasks /V /query /FO CSV} -ComputerName $Machine
                If ($TaskInfo -like "*$User*") {
                    $IsInTasks = $True
                    $IsInTasks = ""
                #Prepare to export to a csv list
                New-Object -TypeName PSCustomObject -Property @{
                    Machine = $Machine
                    IsInAdmins = $IsInAdmins
                    IsInSQL = $IsInSQL
                    IsInTasks = $IsInTasks
                    Services = $Services -Join " "
                    } | Select-Object -property Machine, IsInAdmins, IsInSQL, IsInTasks, Services | Export-Csv $OutputFileName -Append -NoTypeInformation -UseCulture
                Write-Host "$($Machine): No permission to connect."
                Add-Content $ErrorOutputFileName -Value "$($Machine): No permission to connect."
            Write-Host "$($Machine): Unable to ping."
            Add-Content $ErrorOutputFileName -Value "$($Machine): Unable to ping."
    Add-Content -Path $OutputFileName -Value "`nFinished."