Jaa


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.

Comments

  • Anonymous
    January 01, 2003
    @Dr. D: I'm sorry you feel that way. But if you would have read the whole post, you would have seen that the complete working function is posted in the script repository at gallery.technet.microsoft.com/Get-ServiceAccountUsage-b2fa966f Martin.

  • Anonymous
    January 01, 2003
    Thanks for the script, was just about to write one like this myself when i found yours :)
    On a sidenote: Your script will only work on english operating Systems. Localized Versions (at least the german one) will fail to find the correct tasks because "run as user" and the local Service names vary. I can give you the german ones if you are interrested but i guess it would be too much of a hussle to Switch the locale in the script on the fly.

    One more Thing which might be nice for other users - if you build this block (that i copied out of one of my scripts) into yours you can also allow to select a OU and Sub-OUs to scan for accounts:

    #resolve computer names from OU
    $ComputerOU = 'OU='[DN OF OU HERE]'
    $DomainControllerFQDN = '[FQDN of DC HERE]'
    $sess=new-pssession -computername $DomainControllerFQDN
    $CommandRes = invoke-command -session $sess -scriptblock {import-module ActiveDirectory}
    $ImportRes = Import-Pssession -Session $sess -module ActiveDirectory -Prefix Rem -Commandtype All
    $CompList = Get-RemADComputer -SearchScope Subtree -Filter {Enabled -eq "True"} -Properties * -SearchBase $ComputerOU | Select-Object -ExpandProperty CN

    $ComputerName = $CompList

    try
    {
    Remove-PSSession -Computername $DomainControllerFQDN -ErrorAction Stop
    }
    catch [Exception]
    {
    #silently ignore
    }


    you would - of course, build that in through Parameters but it does ist job.

    Also note that the script will fail to query 2012 and up Servers for Tasks failing with "The task XML contains a value which is incorrectly formatted or out of range."

    I'm currently at getting a alternative query in place for those Systems

    Keep up the good work!

    • Anonymous
      May 24, 2016
      I really like the idea of selecting the computers by OU but I am not sure how to incorporate this code into the original function? Could you provide some guidance? thanks
  • Anonymous
    February 21, 2013
    The comment has been removed

  • Anonymous
    March 04, 2013
    The comment has been removed

  • Anonymous
    February 12, 2015
    Hi

    For W2012 did you find a fix for the formatted or out of range error

    Thanks

  • Anonymous
    November 25, 2015
    Thx for this awesome script. I have exact the same problem as Guillaume. For Server OS > 2008R2, i am running into this: ERROR: The task XML contains a value which is incorrectly formatted or out of range

  • Anonymous
    November 25, 2015
    Thx for this awesome script. I have exact the same problem as Guillaume. For Server OS > 2008R2, i am running into this: ERROR: The task XML contains a value which is incorrectly formatted or out of range

  • Anonymous
    November 25, 2015
    Got the solution:
    i tried to run this script from a server 2008R2 Server on a server2012. This is the problem. If you run the script from a server2012, then it is working like a charm.

  • Anonymous
    February 11, 2016
    Thank you Martin! Worked great once I registered the script.

  • Anonymous
    November 07, 2016
    Just ran your script and it's fantastic, thanks.I did find one thing though. The Name column width causes it to truncate causing loss of additional information used to determine which service or application it is. Normally this wouldn't be an issue, but in my case I have multiple services that are close in name. Is there a way to make it wider or is the value fixed?

  • Anonymous
    February 23, 2017
    Script worked perfectly for me. To avoid the task error, I run the script off a W2012R2 machine. Thanks Again!

  • Anonymous
    July 04, 2017
    The comment has been removed