Many applications use Windows services that run using a specific user account (these are often referred to informally as “service accounts”). If a service account’s password expires, services that run using that account won’t be able to start until the administrator changes the password for the account and updates the password for the services. As a result, Windows administrators often enable the “password never expires” setting on these kinds of accounts to prevent service startup problems due to password expiration.

Eventually, however, good security practice dictates that we change service account passwords periodically. Changing an account’s password is simple, but updating the password for all of the services that start using that account can be a time-consuming and error-prone process, depending on the number of servers and services involved.

For example, one application in my environment runs nine different services across five different servers using two different service accounts. Completing a password change for the two service accounts (which, naturally, requires updating all of the services to use the new password) is thus a time-consuming and potentially error-prone manual process.

Using WMI to Update a Service’s Credentials

Fortunately, there is a way to update a service’s credentials programmatically using WMI. The Change method of the Win32_Service class (see https://msdn.microsoft.com/en-us/library/aa384901.aspx) has two parameters, StartName and StartPassword, that specify the account and password for running the service. Since we don’t want to use any of the other parameters for the method, we’ll specify $null in PowerShell.

Listing 1 contains a short sample script that prompts for credentials (a username and password) and configures a service called App Service on the local computer to use the credentials. In Callout A, the script retrieves a PSCredential object into the $credential variable. (The PSCredential object securely stores a username and password.) Callout B shows how the script retrieves the Win32_Service object representing the service into the $service variable, and Callout C shows how the script uses the Win32_Service object’s Change method to update the username and password for the service.

                              
# Listing1.ps1
# BEGIN CALLOUT A
$credential = Get-Credential
# END CALLOUT A

# BEGIN CALLOUT B
$params = @{
  "Namespace" = "root\CIMV2"
  "Class" = "Win32_Service"
  "Filter" = "DisplayName='App Service'"
}
$service = Get-WmiObject @params
# END CALLOUT B

# BEGIN CALLOUT C
$service.Change($null,
  $null,
  $null,
  $null,
  $null,
  $null,
  $credential.UserName,
  $credential.GetNetworkCredential().Password,
  $null,
  $null,
  $null)
# END CALLOUT C

The Win32_Service object’s Change method requires a plain-text copy of the password (rather than a PSCredential object), so the script in Listing 1 retrieves the UserName property and a plain-text copy of the password from the PSCredential object. This means that a plain-text copy of the password is temporarily available in memory for the current process, but WMI encrypts the password over the network before sending it to a remote computer.

The script in Listing 1 is equivalent to opening the Services console (Services.msc), double-clicking on the App Service service, clicking on the Log On tab, entering a username and password, and then clicking OK. Of course, the script could be extended to set the password for a service on a remote computer, set the password for multiple services, and so forth. I wrote a PowerShell script, Set-ServiceCredential.ps1, that does exactly this: It sets the credentials for one or more services on one or more computers.

Introducing Set-ServiceCredential.ps1

The Set-ServiceCredential.ps1 script uses the following command-line syntax:

Set-ServiceCredential.ps1 [-ServiceName] <String[]> [[-ComputerName] <Object[]>]
[-ServiceCredential] <PSCredential> [-ConnectionCredential <PSCredential>] [-Confirm]
[-WhatIf]

The script has only two mandatory parameters: One or more service names (-ServiceName), and a single PSCredential object (-ServiceCredential) that specifies the new credentials for the service(s). The -ComputerName parameter allows you to update the service(s) on one or more computers, and the -ConnectionCredential parameter specifies credentials that have sufficient permissions on remote computer(s) to update the services.

If the list of service names is the first item on the script’s command line, the -ServiceName parameter is optional. This parameter doesn’t support wildcards, but you can specify a single service name or a list (array) of service names (separated by commas). You can specify a service either by its name or its display name (i.e., the name listed in the Services.msc console).

The script supports connecting to multiple computers as well, using the -ComputerName parameter. The default is the local computer. As with the -ServiceName parameter, you can specify a single computer name or multiple computer names (separated by commas). The -ComputerName parameter also supports pipeline input that can contain a list of computer names or objects with a ComputerName property.

The -ServiceCredential parameter specifies the new credentials for the service(s) named in the -ServiceName parameter. Since this parameter is mandatory, PowerShell will prompt for the PSCredential object if you don’t specify this parameter.

If you want to set the credentials for services on the local computer, you must run the Set-ServiceCredential.ps1 script from an elevated PowerShell session (i.e., you must right-click the PowerShell icon and choose Run as administrator). The script ignores the -ConnectionCredential parameter for the local computer because WMI doesn’t support connection credentials for the local computer.

If you want to set the credentials for services on a remote computer, the current account (i.e., the account that’s running the script) must be a member of the local Administrators group on the remote computer. If the current account is not a member of that group on the remote computer, you must use the -ConnectionCredential parameter to specify an account that is a member.

The -Confirm and -WhatIf parameters behave the same as with PowerShell cmdlets: -Confirm specifies that the script should prompt for confirmation before making changes, and -WhatIf specifies that the script should tell what actions it will perform without performing them. When you run the script, however, you will notice that -Confirm is actually the default, because setting service credentials is a high impact change. This means you must do one of the following to make the script run without confirmation prompts:

  1. Specify -Confirm:$false on the script’s command line to explicitly disable confirmation, or
  2. Set the $ConfirmPreference variable to None to disable all confirmation prompts.

Use caution changing the $ConfirmPreference variable, because changing it will affect all subsequent PowerShell cmdlets and scripts in the current scope. See the help topics about_Preference_Variables and about_Scopes for more details.

Using the Script

Let’s go through some examples of how you could use the Set-ServiceCredential.ps1 script to set credentials for services in your environment. I split the long commands in this section onto multiple lines (separated by back-ticks, `) to make the commands easier to read.

1. Set service credentials for the service App Service 1 on the current computer:

                              Set-ServiceCredential "App Service 1"

Since we didn’t specify service credentials, and the -ServiceCredential parameter is mandatory, PowerShell will prompt for the PSCredential object. After you enter credentials, PowerShell will prompt for confirmation before setting the service credentials:

Confirm

Are you sure you want to perform this action?

Performing operation "Set credentials" on Target "Service 'App Service 1' on 'COMPUTER'".

[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):

2. Set service credentials for a service without prompting for confirmation:

                              
$serviceCred = Get-Credential "FABRIKAM\svcAcct"
Set-ServiceCredential `
  "App Service 1" `
  -ServiceCredential $serviceCred `
  -Confirm:$false

This command prompts for credentials for an account, and the sets the service credentials without prompting. This command presumes that we launched the PowerShell window as an elevated process (“run as administrator”). If not, the command will fail with an “access denied” error.

3. Set service credentials for two services on two servers:

                              
$cred = Get-Credential
Set-ServiceCredential `
  -ServiceName "Svc1","Svc2" `
  -ComputerName "server1","server2" `
  -ServiceCredential $cred `
  -Confirm:$false

The script example in this command sets the credentials for two services (Svc1 and Svc2) on two remote computers (server1 and server2). The current user must be a member of the Administrators group on the remote computers.

4. Set service credentials for a service on two remote servers, using connection credentials:

                              
$serviceCred = $Host.UI.PromptForCredential(
  "Service Credentials",
  "Enter new credentials for the services.",
  "",
  ""
)
$connectCred = $Host.UI.PromptForCredential(
  "Connection Credentials",
  "Enter credentials for connecting to the servers.",
  "",
  ""
)
Set-ServiceCredential `
  -ServiceName "Svc1","Svc2" `
  -ComputerName "server1","server2" `
  -ServiceCredential $serviceCred `
  -ConnectionCredential $connectCred
  -Confirm:$false

This example illustrates how to prompt for credentials using custom prompts to clarify which credentials we’re entering. After collecting both sets of credentials, this example runs Set-ServiceCredential.ps1 to set the credentials for two services on two servers.

5. Set service credentials for a service on a list of computers in a text file:

                              
$serviceCred = Get-Credential "FABRIKAM\svcAcct"
Get-Content ComputerList.txt | ForEach-Object {
  Set-ServiceCredential `
    -ServiceName  "App Service" `
    -ServiceCredential $serviceCred `
    -Confirm:$false
}

This example prompts for credentials for an account and then retrieves a list of computers from the file ComputerList.txt, setting service credentials for the service named App Service each computer in the list.

6. Set service credentials for a service on a list of computers in an AD container:

                              
$serviceCred = Get-Credential "FABRIKAM\svcAcct"
Get-ADComputer -Filter { Name -like "*" } `
  -SearchBase "CN=Computers,DC=fabrikam,DC=com" |
  Select-Object -ExpandProperty Name |
  ForEach-Object {
    Set-ServiceCredential `
      -ServiceName  "App Service" `
      -ServiceCredential $serviceCred `
  }

This example prompts for credentials and then sets these credentials for a service called App Service on all computers in the specified container. This example uses the Select-Object cmdlet to select the computer names of each computer. Also, note that this example does not specify -Confirm:$false because this is such a high risk operation.

Setting Service Credentials Made Easy

Set-ServiceCredential.ps1 eliminates the time-consuming and error-prone process of setting service credentials from the GUI. Add this script to your toolbox and you no longer need to set service credentials the hard way.

Download the Script: Set-ServiceCredential.zip