The Administrator account is a built-in account on every Windows computer connected to your network. Managing the password for this account on a computer is important because it provides full administrative access to that computer. Group Policy provides a way to set the Administrator account's password, but the Group Policy Object (GPO) that you use to configure this setting doesn't store the password securely. It merely obfuscates the password rather than encrypts it. Setting administrative passwords using Group Policy goes against Microsoft's recommendations because it's not secure.
One possible solution to this problem is to use a script to reset the Administrator password on your computers. You can do this relatively easily using a script, such as the VBScript script shown in Listing 1 or the Windows PowerShell script shown in Listing 2. However, there are two main problems with this approach:
- The password is stored in plaintext directly in the script, which is even less secure than the obfuscation provided by Group Policy.
- The account name, Administrator, is hard-coded in the script, so you have to change the script if you have a policy that renames the Administrator account or if you use a non-English version of Windows.
You can overcome both of these problems using PowerShell.
Overcoming Problem 1
The Microsoft .NET Framework provides the SecureString object, which is a secure representation of a string. A number of PowerShell cmdlets and .NET objects use SecureString objects rather than plaintext strings. For example, the New-ADUser cmdlet's -AccountPassword parameter uses a SecureString argument. PowerShell makes it easy to create a SecureString object using the Read-Host cmdlet with the -AsSecureString parameter. For instance, the following PowerShell command masks the string on the screen as you type it and stores it in a SecureString object:
One thing you can't do directly in PowerShell is retrieve the secure string stored in a SecureString object as a plaintext string. However, the ConvertTo-String function in Listing 3 will do this. It uses the .NET Framework to copy the secure string in the SecureString object and convert it into a BSTR (basic string). It then copies the BSTR and converts it into a plaintext string, storing it in a .NET String object.
After this, the function frees the BSTR from memory. This step is necessary because a BSTR is an unmanaged data type, which means the .NET Framework doesn't free it from memory automatically. Finally, the function outputs the resulting String object, which contains the plaintext version of the secure string.
When creating new passwords, it's helpful to provide two prompts (enter and confirm), then compare the strings to ensure they match. That's the purpose of the New-SecureString.ps1 script shown in Listing 3. The script prompts for two SecureString objects and uses the ConvertTo-String function to compare the strings. If the strings aren't equal, the script repeats the prompt. Finally, the script outputs a SecureString object. Figure 1 shows the script in action.
PowerShell also makes it easy to convert a SecureString object into an encrypted standard string so you can securely store and reuse it. The ConvertFrom-SecureString and ConvertTo-SecureString cmdlets provide this functionality. The ConvertFrom-SecureString cmdlet converts a SecureString object into an encrypted standard string. The ConvertTo-SecureString cmdlet converts an encrypted standard string into a SecureString object.
The ConvertFrom-SecureString cmdlet achieves its encryption using either the Windows Data Protection API (DPAPI) or a specified encryption key (using the -Key or -SecureKey parameter). If you omit the -Key or -SecureKey parameter, the cmdlet uses the Windows Data Protection API (DPAPI), which in practice means that the encrypted string can only be decrypted by the same user account that encrypted it. If you save the encrypted string to a file and try to decrypt it from a different account, the decryption will fail.
Figure 2 shows an example of creating, encrypting, and decrypting a SecureString object. The first command creates the SecureString object. The second command converts the SecureString object into an encrypted standard string and writes it to a text file. The third command (Get-Content EncPass.txt) outputs the encrypted content of the text file. The fourth command decrypts the password and stores it in a second SecureString object (the $secureString2 variable). The final command shows that the $secureString2 variable does, in fact, contain a SecureString object.
I didn't use the -Key or -SecureKey parameters to encrypt the password in Figure 2, so I can only decrypt the password using the same user account. Figure 3 shows what happens when I log on using a different account and try to decrypt the encrypted password. As you can see, the decryption fails and PowerShell throws an error.
In summary, to overcome the problem of storing plaintext passwords in a script, you can create a SecureString object that contains the password, then use the ConvertFrom-SecureString cmdlet to encrypt the password (as an encrypted standard string) and store it in a text file. When needed, you can then decrypt the encrypted password and convert it back to a SecureString object using the ConvertTo-SecureString cmdlet.
Overcoming Problem 2
As I noted previously, all Windows machines have a built-in Administrator account, but the account isn't always named "Administrator." Non-English versions of Windows use a different name for the account, and it's possible to rename the account manually or with a policy. Because the name might be different on different machines, it's better if the script figures out the correct account name programmatically.
As you might know, all accounts have a SID that remains constant throughout the account's lifetime. An example of a SID is S-1-5-21-4535438673-234387905-476317865-1004. The part of the SID following the last hyphen (-) is called the Relative Identifier (RID). In this example, the account's RID is 1004. The portion of the SID before the RID is specific to the domain or computer where the account resides.
The problem you run into is that local accounts don't have a property containing a string copy of the SID. Instead, local accounts have the objectSid property, which is a byte array rather than a string. To convert the objectSid value into a string, you can create a SecurityIdentifier object using the objectSid property as input. The Get-AdminAccount.ps1 script shown in Listing 4 illustrates this technique. This script gets all the child objects from the current computer and skips objects that aren't users. Callout A highlights how the script creates a SecurityIdentifier object from the objectSid property. If the SecurityIdentifier object's Value property (i.e., the string representation of the SID) ends with 500, the script has discovered the Administrator account. After it finds the Administrator account, it outputs the account's name and SID, then exits from the foreach loop using the break statement. Figure 4 shows sample output from the script.
In summary, to overcome the problem of possibly having a different name for the built-in Administrator account, you can enumerate through all the user objects on a computer and create a SecurityIdentifier object for each one. If the Value property of the SecurityIdentifier object ends with 500, you've found the built-in Administrator account.
Putting It All Together
The Reset-LocalAdminPassword.ps1 script combines these two solutions into a single easy-to-use script. You can download this script (as well as the other code I presented here) by clicking the Download the Code button near the top of the page. The syntax for the Reset-LocalAdminPassword.ps1 script is:
[-Password <SecureString>] [-Confirm] [-Verbose] [-WhatIf]
The -ComputerName parameter allows you to specify one or more computer names, so you can reset the local Administrator password on multiple computers. The -ComputerName parameter name is optional. This parameter accepts pipeline input. If you don't specify this parameter, the script will reset the Administrator account's password on the local computer.
The -Password parameter is required. If you don't specify this parameter, the script will prompt for a password. Because this parameter's argument is a SecureString object, you can create a text file containing an encrypted standard string to securely store the password. (I recommend using the New-SecureString.ps1 script from Listing 3 to create the SecureString object because it makes you type the password twice.)
Like many of the PowerShell cmdlets, the script supports the -Confirm, -Verbose, and -WhatIf parameters. If you specify the -Confirm parameter, the script will prompt you before taking action. If you include the -Verbose parameter, the script will produce verbose output. If you use the -WhatIf parameter, the script will display the action it will perform rather than performing it.
Perceptive readers might note that the SetPassword method for the user object (see Listing 1 and Listing 2) requires a plaintext copy of the password, so Reset-LocalAdminPassword.ps1 has to use the ConvertTo-String function (see Listing 3) to convert the SecureString object into a String object first. This means that the plaintext password is temporarily visible in RAM while the script executes. Unfortunately, there's no way around this limitation because the SetPassword method can't use a SecureString object. However, this is still more secure than the obfuscation provided by Group Policy or storing the password in plaintext in the script.
Seeing the Scripts in Action
Figure 5 shows a sample PowerShell session in which the New-SecureString.ps1 and Reset-LocalAdminPassword.ps1 scripts are executed. The first command uses the New-SecureString.ps1 script to create an encrypted password and store it in a file named P.txt. Remember that only the account that creates this file can decrypt the password. The second command creates a SecureString object by decrypting the password stored in the P.txt file. Finally, the last command uses the Reset-LocalAdminPassword.ps1 script to reset the Administrator account's password on the local computer using the SecureString object. Naturally, elevated privileges are required to reset the Administrator password on the local machine. (I right-clicked the PowerShell icon and used the Run as administrator option. The title bar of the PowerShell window reflects this.)
Because the Reset-LocalAdminPassword.ps1 script's -ComputerName parameter accepts pipeline input, you can also use a command like this:
Reset-LocalAdminPassword.ps1 -Password $secureString
This command would reset the local Administrator password for all the computers named in the ComputerList.txt file.
Administer the Administrator Account
There's no need to insecurely store an Administrator account password in a GPO or in plaintext in a script. You can use a SecureString object with the Reset-LocalAdminPassword.ps1 script to securely reset the Administrator account password for the computers in your network.