# encoding: ascii
# api: powershell
# title: Get-ServiceAccounts
# description: Script will help discover any service accounts that are currently being used.  I have been using this script for about a month in production.  While your mileage may vary this script is strictly read only, thus -confirm, and -whatif are not supported.  
# version: 0.1
# type: class
# author: Glenn Sizemore glnsize
# license: CC0
# x-poshcode-id: 1156
# x-archived: 2009-06-14T23:56:47
#
# V.1	Initial Build
# V.1.1	Optimized GWMI query
# V.1.2	Added logic to record the cause of a failed query
#
#requires -pssnapin PSCX
#Author:     Glenn Sizemore glnsize@get-admin.com
#Purpose:    Scan a remote system for any services running under a non standard account.
#            Standard accounts would be considered, NT AUTHORITY\NetworkService, 
#            NT AUTHORITY\LocalService, LocalSystem, or .\ASPNET
#            
#Usage:      PS > Get-ServiceAccounts [-Target <string>] [-Path <string>] [-verbose] [-Credential <PSCredential Object> ]
#            
#            -Target(optional)  
#                  can be either a string or an array of strings.
#            -Path(optional)    
#                 Path to an item containing a list of Targets
#            -Verbose(optional) 
#                 turns on verbose logging to the console
#            -Credential(optional) 
#                  should only be used with V2, uses alternate credentials when 
#                querying target for services.
#
#Returns:    PSCustom Object
#
#Example:    Get-ServiceAccounts -Target "host1","host2","host3" -Path "C:\temp\otherhosts.txt" -verbose
#            
#            this command will combined the targets: host1, host2, and host3 with all targets located in 
#            the file otherhost.txt, and scan all of them for unique service accounts.
#

param($target,
[string]$Path,
[switch]$verbose,
[System.Management.Automation.PSCredential]$credential = ($null)
)
begin 
{
    # Create an empty object to hold all service accounts found.
    $SvcAccounts = @()

    #Save the current $ErrorActionPreference so we can restore it.
    $ErrActionSave = $ErrorActionPreference 
    $Verbosesave = $VerbosePreference
    $warningSave = $WarningPreference

    #If a PSCredential is supplied then wmi query will be ran under that account.
    if ($credential)
    {
        Write-Host "there is a known bug using -credential in V1..."
        $gwmiquery = {Get-WmiObject -Class Win32_Service -ComputerName $computer -Credential $credential `
            -property name, startname, caption, StartMode `
            -filter 'NOT Startname LIKE "%NT AUTHORITY%" AND NOT Startname LIKE "LocalSystem" AND NOT Startname LIKE "ASPNET"'}
    }
    else
    {
        $gwmiquery = {Get-WmiObject -Class Win32_Service -ComputerName $computer -property name, startname, caption, StartMode `
            -filter 'NOT Startname LIKE "%NT AUTHORITY%" AND NOT Startname LIKE "LocalSystem" AND NOT Startname LIKE "ASPNET"'}
    }

    if ($Verbose) {$VerbosePreference = "Continue"; $WarningPreference = "Continue"}

    #Workhorse...
    function ProcessTarget ($computer) 
    {
        $obj = @()

        #Set ErrorAction to silent... we'll handle the error's ourselves.
        $ErrorActionPreference = "SilentlyContinue" 

        Write-Verbose "querying $computer ..."

        #call the Query from above.
        $services = &$gwmiquery 

        #set erroraction back there sould be no error's from here on out.
        $ErrorActionPreference = $ErrActionSave 

        Write-Verbose " $($services.count) services located on $computer using "
        foreach ($service in $Services)
        {

            # If there was an error while attempting the gwmi call, there will be only one error.
            # We check that error to help annotate why we were unsuccessful.
            switch -regex ($Error[0].Exception) 
            {
                "The RPC server is unavailable"
                {
                    Write-warning "RPC Unavailable on $computer"
                    $obj += "" | Select @{e={$computer};n='Target'},@{e={"RPC_Unavalable"};n='SvcName'}
                    continue
                } 
                #vista
                "Access denied"
                {
                    Write-warning "Access Denied on $computer"
                    $obj += "" | Select @{e={$computer};n='Target'},@{e={"Access_Denied"};n='SvcName'}
                    continue
                }
                #XP/Server 2k3
                "Access is denied"
                {
                    Write-warning "Access Denied on $computer"
                    $obj += "" | Select @{e={$computer};n='Target'},@{e={"Access_Denied"};n='SvcName'}
                    continue
                }
                $null 
                {
                    $obj += "" | Select @{e={$computer};n='Target'},
                    @{e={$service.name };n='SvcName'},
                    @{e={$service.startname };n='SvcAcccount'},
                    @{e={$service.caption };n='SvcDes'},
                    @{e={$service.StartMode };n='StartMode'}
                } 
            }
            $Error.clear()
        }
        return $obj
    } 
}
process 
{
    $Targets = @()
    if ($Path){$Targets = Get-Content $Path}
    if ($Target){$Targets += $target}

    foreach ($computer in $Targets) 
    {
        ping-host -HostName $computer -Count 1 -Quiet -Timeout 1| Where-Object { $_.loss -ne 100} | `
        ForEach-Object {$SvcAccounts += ProcessTarget $_.host}
    }

}
End 
{
    $ErrorActionPreference = $ErrActionSave
    $VerbosePreference = $Verbosesave
    $WarningPreference = $warningSave
    Write-Output $SvcAccounts
}
