Expert Solution for the 2011 Scripting Games Beginner Event 4: Use PowerShell to Identify Non-Standard Windows Service Accounts

Expert Solution for the 2011 Scripting Games Beginner Event 4: Use PowerShell to Identify Non-Standard Windows Service Accounts

  • Comments 2
  • Likes

Summary: Microsoft Windows PowerShell MVP Tobias Weltner solves 2011 Scripting Games Beginner Event 4 and finds nonstandard Windows service accounts.

Microsoft Scripting Guy, Ed Wilson, here. Our expert commentator for Beginner Event 4 is Dr. Tobias Weltner.

beg4-1

Dr. Tobias Weltner is a Germany-based writer, software architect, and trainer. Dr. Weltner has published more than 100 books on IT topics such as Windows operating systems, security, and script automation. As a software architect, he designed rich and innovative commercial script development environments such as SystemScripter (VBScript) and PowerShellPlus (Windows PowerShell), and he is cofounder of Windows PowerShell communities, including powershellcommunity.org and powershell.com. He currently specializes in high-profile Windows PowerShell training and consulting throughout Europe. Dr. Weltner graduated from Hannover Medical School in 1998, and he was a research doctor in trauma surgery. He enjoys travelling and supports youth exchange at the local Rotary club.

Worked solution

To find services with nonstandard accounts, it is necessary to use WMI rather than the Get-Service cmdlet because Get-Service does not return that kind of information. That is why the solution uses Get-WMIObject and queries for all instances of the class Win32_Service.

To examine remote computers, a list of computers is submitted to the computername parameter. Get-WMIObject takes care of remoting by itself. There is no need to have Windows PowerShell installed on the target computers. You will need local administrator privileges on the target machines, though, and the firewall needs to allow access. If one of the target computers is unavailable (offline, missing privileges, firewall), an error message is emitted by Get-WMIObject.

To identify nonstandard accounts in the results, the script uses a master list of authorized standard accounts. It then filters the results on the client side with Where-Object and uses the notcontains operator to check whether a given service uses an account that is not in the approved account list. Only services with nonstandard accounts are returned.

Becuase the results can come from many different computers, the script uses Select-Object to make sure the property __SERVER is made visible, which contains the name of the computer where the information originated.

$accounts = 'LocalSystem', 'NT Authority\LocalService', 'NT Authority\NetworkService'

$computer = '127.0.0.1', '10.10.10.33', 'demo5'

 

Get-WmiObject Win32_Service -ComputerName $computer |

  Where-Object { $accounts -notcontains $_.StartName } |

  Select-Object __SERVER, StartName, Name, DisplayName

This solution, while simple and straight-forward, still has some room for improvement. First, it uses client-side filtering rather than server-side filtering, so all service information first needs to travel across the network which increases bandwidth and CPU load. Second, if a system is unavailable, the script generates an exception, and querying the remaining computers may be interrupted depending on the type of exception. Third, if no nonstandard accounts were found, the script returns nothing, and while this is expected, a user might assume that the script failed.

Network and performance considerations can be addressed by using server-side filtering. The improved script submits a WQL query to Get-WMIObject, minimizing the data that needs to travel back to the client. There is no longer a client-side Where-Object needed (note that WQL is an independent WMI query language and not PowerShell code, so it is using its own operators and syntax), as shown in the following example:

$computer = '127.0.0.1', 'demo6', '10.10.10.33', 'demo5'

$wql = 'Select Name, DisplayName, StartName, __Server From Win32_Service WHERE ((StartName != "LocalSystem") and (StartName != "NT Authority\\LocalService") and (StartName != "NT Authority\\NetworkService"))'

Get-WmiObject -Query $wql -ComputerName $computer |

  Select-Object __SERVER, StartName, Name, DisplayName

Premature abortion, which may occur with Access Denied type of exceptions, can be handled by processing computers sequentially, which also introduces the opportunity to implement individual error handling. When systems are unavailable, the script returns warning messages rather than raw exceptions, as shown in the following example:

$computer = '127.0.0.1', '127.0.0.2', '10.10.10.33', 'demo5'

$wql = 'Select Name, DisplayName, StartName, __Server From Win32_Service WHERE ((StartName != "LocalSystem1") and (StartName != "NT Authority\\LocalService") and (StartName != "NT Authority\\NetworkService"))'

$computer |

  ForEach-Object {

    $computer = $_

    try {

    Get-WmiObject -Query $wql -ComputerName $computer -ErrorAction Stop |

    Select-Object __SERVER, StartName, Name, DisplayName

    }

    catch {

      Write-Warning ('{0:,-30}{1}' -f $computer, $_.Exception.Message)

    }

 }

To create a robust company-wide solution, reporting should not just contain nonstandard services, but it should also log access problems and successfully queried computers with no nonstandard services. While this is beyond the scope of this task, here is an example of such a professional solution:

<#

.SYNOPSIS

   Identifies services with nonstandard accounts

.DESCRIPTION

   Retrieves services that use accounts other than LocalSystem, LocalService or NetworkService

.PARAMETER computername

   one or more computernames or IP addresses

   you will need local administrator privileges, and the firewall needs to allow access

   to enable firewall access, run this command on target machines:

   netsh firewall set service type = remoteadmin mode = enable

.EXAMPLE

   Get-NonstandardService

   lists services with nonstandard accounts on local machine

.EXAMPLE

   Get-NonstandardService -computername 10.10.10.33

   lists services with nonstandard accounts on remote machine with IP 10.10.10.33

.EXAMPLE

   Get-NonstandardService -computername 10.10.10.33, 127.0.0.1, serv12-1, client3

   lists services with nonstandard accounts on four machines (including local system)

.LINK

   http://www.powershell.com

#>

 

function Get-NonstandardService {

  param(

  [String[]]

  $computername = '127.0.0.1'

  )

 

  # server-side WMI query to minimize network traffic and maximize performance

  $wql = 'Select Name, DisplayName, StartName, __Server From Win32_Service WHERE ((StartName != "LocalSystem") and (StartName != "NT Authority\\LocalService") and (StartName != "NT Authority\\NetworkService"))'

 

  # examine all computers submitted:

  $computername |

  ForEach-Object {

    Write-Progress 'examining computer:' $_

 

    # create new object to return information

    $rv = New-Object PSObject | Select-Object Computer, Result, Name, DisplayName

    $rv.computer = $_

 

    # search for nonstandard services

    try {

      # always return result as array

      $result = @(Get-WmiObject -Query $wql -ComputerName $rv.computer -ErrorAction Stop | Sort-Object DisplayName)

 

      # no results?

      if ($result.Count -eq 0) {

        # then all services use standard accounts, good:

        $rv.Result = 'OK'

        $rv

      } else {

        # return a result set for each nonstandard service

        $result | ForEach-Object {

          $rv.Computer = $_.__Server

          $rv.Name = $_.Name

          $rv.DisplayName = $_.DisplayName

          $rv.Result = $_.StartName

          $rv

        }

      }

    }

    catch {

      # WMI was unable to retrieve the information

      switch ($_) {

        # sort out most common errors and return qualified information

        {          $_.Exception.ErrorCode -eq 0x800706ba} { $rv.Result = 'WARN: Unavailable (offline, firewall)' }

        {          $_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } { $rv.Result = 'WARN: Access denied' }

        # return all other non-common errors

        default { $rv.Result = 'WARN: ' + $_.Exception.Message }

      }

      # return error information

      $rv

    }

  }

}

Tobias, thank you very much for taking the time to solve and to write about your solution to Beginner Event 4.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Dear Tobias,

    a brilliant, well explained solution INCLUDING detailed error handling!!!!

    I hope nobody will argue, that this is a german conspiracy, but this time I have

    no complaint at all!

    Sorry, but true :-)

    beste Grüße aus (Fußballmeister-Stadt in Spe) Dortmund

    kind regards from (soccer champion town in spe) Dortmund

    Klaus (Schulte)

  • trying to leave a questio/ comment on the ones I submit to learn more.  I like the array for the usernames; solves my problem of the multiple where-object lines in my mess.  I did run into an issue where some of our line-of-business apps had the standard service names but it was "Network Service" not "NetworkService".  I built a -notlike network*service for the space.  Would there have been another way to detect for this or was that the proper way, and are those technically two separate users or the same?