PowerShell Pipeline
Managing Services Part 3: Updating the Password for a Service Account in PowerShell
Here's how to use PowerShell to change a service account password that may be shared by multiple users.
More on This Topic:
As any system administrator knows, using a domain account as a service account is a great practice. Of course, making sure that the account is using the lowest level of privileges is just as important as well. One of the other things that you should be doing is making sure you update the passwords on those service accounts at least yearly as well to ensure that nothing goes stale.
Changing a service account password can be an interesting ordeal depending on your environment. If you are lucky enough to be in a Windows 2012 R2 domain, then you have the use of managed service accounts to make it easier (I'm intentionally ignoring the managed service accounts in Windows 2008 R2 as they do not allow the use of a single account for multiple systems). The old fashioned approach is to reset the password in Active Directory and then go to each server that has the service account running a service and set the password on the service, restart it and make sure that there are no issues with the service once it has come back and running.
PowerShell can make this otherwise tedious task a bit easier by allowing us to set the password on the service as well as performing the restart of the service. Of course I say it is easier, but there are some things to keep in mind while you are starting to work through this and I will be sure to cover those areas to ensure that things make sense.
Using WMI To Change a Password
As the title suggests, we are going to be using WMI as our preferred approach to changing a password for a service account. Unfortunately, using the *-Service cmdlets provides no real way to accomplish this task.
Let's assume we have an account called SomeServiceAccount that is running a service. In this case it is called Bonjour Service and it has come upon the six-month requirement to change the password on this service as well as other services. We are only going to focus on the Bonjour Service and step through the process to first find the service and then to ensure that we successfully update the service account password.
It is believed that the service account is being used on multiple systems, but first we want to test it out on a single system to ensure that it works properly before we scale out the change.
I first want to get the win32_Service WMI object that contains the service in question so I can investigate its methods to see if it has something that I can use. I will start out by looking for any services that are using SomeServiceAccount as the StartName because I don't want to assume that only the Bonjour Service is using this account and then have other services break as soon as I started changing passwords.
$Service = Get-WmiObject -Class Win32_Service -Filter "StartName LIKE '%\\SomeServiceAccount' OR StartName LIKE 'SomeServiceAccount@%'"
$Service | Select Name, StartName, State
Ok, this system only has a single service using this account which is good. Now I need to dive into the object the see what is available and I can hopefully find a useful method to assist in changing the password.
$Service | Get-Member -MemberType Method
I found the method called Change which is what I am looking for to aid in updating the password on my system. In fact, if we view the method overloads of Change, we can see where there is a parameter that allows a new password.
This is looking for a string value to update the password, not a secure string. If you want to use something like Read-Host with the –AsSecure parameter, then you will have to do a little more work to get the password to apply properly to the service object.
What might seem confusing is that there are all of these other parameters in the Change method that exist both before and after the password parameter. How in the world do we ensure that we are not changing these as well when we are updating the password? The answer is to use $Null and pass that to each parameter that we do not care about. By doing this, the Change method will essentially ignore that parameter when it is applying the change to the service.
Jumping back to the issue of using a secure string with the string, I will demonstrate an approach to handling this so you can ensure that what you are typing is not visible to anyone that might be passing by.
#Get the secure string password
$Password = Read-Host -Prompt "Enter password for $RunAsAccount" -AsSecureString
$Password
At the prompt, I type in my password and click OK. Now my password is in a secure string. While this is great, it will not work that well when passed to the Change method. I need to actually convert this back to a regular string before it will be acceptable for use with the method. I will do this using a couple of commands below.
$BSTR = [system.runtime.interopservices.marshal]::SecureStringToBSTR($Password)
$Password = [system.runtime.interopservices.marshal]::PtrToStringAuto($BSTR)
$Password
As you can see, my password which is obviously not a good password has been pulled from the secure string and is now in clear text as a string which can now be passed to the Change method. There will be some cleanup that has to be done at the end of this to ensure that we are not accidently leaving data behind that could be picked up and used to figure out the password.
Now that we have our password (in this case I would actually run the commands again to set a more complex password), we can move forward with changing the password on the service. The password parameter is the eighth item in the method so everything else needs to be set to $Null.
$Service.Change($Null,$Null,$Null,$Null,$Null,$Null,$Null,$Password,$Null,$Null,$Null)
Now all that is left to do is to restart the service to make sure the change takes effect and that we have no issues.
$Service.StopService().ReturnValue
$Service.StartService().ReturnValue
And with that, we have successfully changed the password of the account that is running the service! As I mentioned earlier, we still need to do some cleanup in order to make sure that no remnants of the password string can be found.
Remove-Variable Password,BSTR
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
By doing this, we have made sure to remove all of the variables that hold anything that sensitive and then I made sure to zero out the pointer that holds the password in unmanaged memory and then frees it.
With that, I have shown you how you can update the password on a service object. You can take this and write a function that could easily assist in updating all of the services in your domain rather than spending a lot of time going from system to system and performing the same task!
About the Author
Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.