PowerShell: Get-ServiceAccountUsage

If you ever needed to know if and where a certain user account was being used to run a service, a scheduled task or an application pool, then you’ll find this post helpful.

SOX regulations, security procedures or your boss may require you to have the password periodically changed for accounts running services, scheduled tasks or IIS application pools.
Before you go ahead and change the password, you should either have a detailed list of who (the user account) runs what (the service, task or application pool) and where (on which computer), or use a script to get this information, so after you change the account’s password in the domain, you can go to all those services, scheduled tasks or application pools on the different computers and change the password settings.

Since I needed to do just that, I’ve decided to write a PowerShell function that can scan a computer (or an array of computers) and list all those services, scheduled tasks or application pools.

Services
Getting the services information was easy, using the Get-WmiObject cmdlet and querying the Win32_Service Class, especially getting the StartName property:

 Get-WmiObject -Namespace root\cimv2 -Class Win32_Service -ComputerName $Computer ...

Filtering

The most important issue was to filter the services, and get only the ones running by a specific user account, or by all accounts other than the built-in ones (e.g. LocalSystem, LocalService, NetworkService, etc.).

Filtering using the –Filter parameter or using the ‘where’ directive in the full WQL query has better performance than filtering using the Where-Object cmdlet.

This way, instead of retrieving all the services from the remote machine and only then filtering out the specific ones, you get the filtered out list right away.

Related reading: http://technet.microsoft.com/en-us/magazine/2009.09.windowspowershell.aspx

Scheduled Tasks

Getting the Scheduled Tasks information was a different challenge. I thought about using the Schedule.Service com object, and parsing the xml property:

 $sch = New-Object -ComObject Schedule.Service
$sch.Connect($Computer)
$tasks = $sch.GetFolder("\").GetTasks(0)
$tasks | Where-Object { ([xml]$_.xml).Task.Principals.Principal.UserID -eq $UserAccount }

But the Schedule.Service com object is available only on Windows Vista and above, and since I needed it to run on Windows 2003 machines too, I ended up using the plain and simple native SCHTASKS command:

 SCHTASKS /QUERY /S $Computer ...

I used the /FO CSV and the /V switches to have a verbose CSV formatted output, and the ConvertFrom-CSV cmdlet to get the results back into an array of PSCustomObjects.

 

Filtering

This time, I needed to filter the results using the Where-Object cmdlet which accepts a scriptblock in the –FilterScript parameter. So for the specific user (“explicit” scenario) I used the –like comparison operator:

 $TaskFilter = { $_.{Run As User} -like $UserAccount }

For the non-built-in accounts (“implicit” scenario), I used the –notcontains comparison operator with an array of strings to exclude:

 $TaskSystemAccounts = @("INTERACTIVE", "SYSTEM", "NETWORK SERVICE", "LOCAL SERVICE",
 "Run As User", "Authenticated Users", "Users", "Administrators", "Everyone", "")
$TaskFilter = { $TaskSystemAccounts -notcontains $_.{Run As User} }

IIS ApplicationPools

Getting the IIS Application Pools information depends on the operating system version, or to be exact: it depends on the IIS version.

For IIS 6 (on Windows 2003), the MicrosoftIISv2 WMI namespace and the IIsApplicationPoolSetting class had the all information I needed. The instance’s WAMUserName property contains the ApplicationPool identity, and when the AppPoolIdentityType equals to 3 (MD_APPPOOL_IDENTITY_TYPE_SPECIFICUSER) it means that it is a specific user and not any of the built-in accounts.

Although the MicrosoftIISv2 WMI namespace may be available on IIS 7/7.5 (if you’ve installed the IIS 6 WMI compatibility on the machine), but the correct way would be to query the WebAdministration WMI namespace (part of the IIS Management Scripts and Tools Web-Server role services)

The ProcessModel .UserName (in the ApplicationPool class) is the property that contains the ApplicationPool identity, and when the ProcessModel.IdentityType equals to 3, it means that it is a specific user and not any of the built-in accounts.

Alternate credentials (and splatting paramters)

After testing the function a few times, it occurred to me that I (or anyone else using the function) may want to use alternate credentials to connect to the remote computer(s).

Lucky me, the Get-WmiObject has a –Credential parameter that accepts a PSCredential object.

So I decided to use the splatting technique, by creating a hashtable with all parameters to be passed to the cmdlet, and add or remove parameters if needed. For example, WMI does not support connecting to the local computer using credentials. So if the user supplied alternate credentials and the computer being queried is the local machine, the –Credential needs to be removed from the parameters hashtable.

 if (@("$ENV:COMPUTERNAME","localhost","127.0.0.1","::1") -contains $Computer) { 
 $ParamServices.Remove("Credential")...

For the Scheduled tasks, there are two parameters: /U and /P that accept the User and Password (respectively) for the remote connections.

IMPORTANT: Please note that the value of the password is translated into clear text before passed to the command.

 

Parameter Sets

Since I needed to have two options for running the function:

  A. When I know which account to look for (the exact name, or using wildcards). A.k.a. the “Explicit” scenario.

  B. When I want the function to get the Services, Scheduled Tasks and ApplicationPools that are running under any account other than the built-in accounts.  A.k.a. the “Implicit” scenario.

I created two ParameterSets accordingly, and assigned the -UserAccount parameter in the Explicit ParameterSet, and the -NonSystemAccounts in the Implicit ParameterSet.

This way, I got my two options for running the function. See the two syntax lines in the snapshot below (Click to enlarge).

Get-Help Get-ServiceAccountUsage

Then I only had to check what ParameterSet was used, and set the relevant filters accordingly:

 switch ($PsCmdlet.ParameterSetName) {
    'Explicit'  { 
        $ServiceFilter = "StartName LIKE '$(($UserAccount).Replace("\","\\").Replace("*","%"))'"
        $TaskFilter = { $_.{Run As User} -like $UserAccount }
        $IIS6Filter = "WAMUserName LIKE '$(($UserAccount).Replace("\","\\").Replace("*","%"))'"
        $IIS7Filter = "ProcessModel.UserName LIKE '$(($UserAccount).Replace("\","\\").Replace("*","%"))'"
        break
        } 
    'Implicit'  { 
        $ServiceFilter = "(NOT StartName LIKE '%LocalSystem') AND (NOT StartName LIKE '%LocalService') AND (NOT StartName LIKE '%NetworkService') AND (NOT StartName LIKE 'NT AUTHORITY%')"
        $TaskSystemAccounts = 'INTERACTIVE', 'SYSTEM', 'NETWORK SERVICE', 'LOCAL SERVICE', 'Run As User', 'Authenticated Users', 'Users', 'Administrators', 'Everyone', '';
        $TaskFilter = { $TaskSystemAccounts -notcontains $_.{Run As User} }
        $IIS6Filter = 'AppPoolIdentityType = 3'
        $IIS7Filter = 'ProcessModel.IdentityType = 3'
        break
    }
}

Related Reading: http://blogs.msdn.com/b/powershell/archive/2008/12/23/powershell-v2-parametersets.aspx

 

Managed Service Accounts and Virtual Accounts

Instead of using a regular user account as a service account that you need to manage its password, you can assign a computer (Windows 2008 R2 or Windows 7) a Managed Service Account (a.k.a. MSA).

MSA’s allow you to create a special account in Active Directory that is tied to a specific computer. That account has its own complex password and is maintained automatically. This means that an MSA can run services on a computer in a secure and easy to maintain manner, while maintaining the capability to connect to network resources as a specific user principal.

MSAs cannot be shared between multiple computers, thus cannot be used in server clusters where a service is replicated on multiple cluster nodes.

Virtual accounts in Windows Server 2008 R2 and Windows 7 are "managed local accounts" that do not require any password management and have the ability to access the network with a computer identity in a domain environment (similar to Network service account).

Windows Server 2008 R2 domains provide native support for both automatic password management and SPN management of MSAs.

If the domain controller is on a computer running Windows Server 2008 or Windows Server 2003 and the Active Directory schema has been upgraded to support this feature, managed service accounts can still be used and MSAs passwords will be managed automatically. However, the domain administrator will still need to manually configure the SPNs.

Related reading:  http://technet.microsoft.com/en-us/library/dd548356 and http://support.microsoft.com/kb/2494158

The Get-ServiceAccountUsage function can be downloaded from here: http://gallery.technet.microsoft.com/Get-ServiceAccountUsage-b2fa966f

Please note that it is provided “as is” without warranty of any kind.

 

FAQs:

Q: How do I use the function?

A: The script file contains the Get-ServiceAcco​untUsage function.

You need to "dot-source" the script file, to have the function declared in your scope, and then you can call it with whatever parameters you need. for example:

 PS> CD C:\myScripts
PS C:\myScripts> . .\Get-ServiceAc​​countUsage.ps1
PS C:\myScripts> Get-ServiceAcco​​untUsage -UserAccount 'myDomain\myUse​​r'

 

Stay tuned for the next post on the subject, where we’ll discuss Get-ServiceAccountUsage’s evil twin brother: Set-ServiceAccountUsage.

Martin.