Building Highly-Available Windows Infrastructure: Command-line Style. AD DS. Part 4 — AGPM

Previous part — Building Highly-Available Windows Infrastructure: Command-line Style. AD DS. Part 3 — Management Infrastructure


When we talk about control version systems (CVS), the first thing comes to mind is, of course, program code. In the modern world, one cannot be a decent software developer if they do not use Git or TFS or Mercurial or Subversion etc. But this does not mean only developers benefit from the concept of CVS: Adobe provides designers with its own solutions to manage file versions.
What about us, IT administrators? Given the growing popularity of the infrastructure-as-a-code concept, many IT administrators have already adopted some kind of CVS to store scripts and configurations files.

Today I want to talk about version control for group policies. You probably know that group policies are not exactly text files, therefore, traditional CVSes are not the best choice here. That’s why Microsoft came up with its own solution, which allows us to track changes, compare GPO versions and quickly restore the previous ones: Advanced Group Policy Management (AGPM).

Interesting, that it is not just a CVS, but is also a tool to delegate group policies administration with built-in approval management mechanism.
But even if you work in a small team and do not need GPO management delegation, I still encourage you to use AGPM as a version control system.

AGPM is a part of Microsoft Desktop Optimization Pack, which is a free software set available to Microsoft Software Assurance subscribers. Here’s the official documentation where you can learn more about the product.


AGPM is NOT a substitute for a proper Active Directory backup.

Variables used in the article:


Prepare AD DS

AGPM consists of two parts: a server and a client. The server, unfortunately, cannot be made highly available and, usually, is installed once per AD DS forest. I recommend to use a separate machine for that. The client should be installed to administrative workstations.


Paulo Francisco Viralhadas has just written an interesting article on having multiple AGPM servers per domain.

As a part of preparations, we shall create a server computer account, several groups and a service account. For the service account we will use a Group-Managed Service Account (gMSA).

gMSA is a new type of account, which became available starting Windows Server 2012. gMSA is a mix between a computer and user accounts. The main advantage is that its password is maintained by Active Directory and is updated regularly and automatically.
From the Computer class, gMSA adopted “$” sign at the end of sAMAccountName and a requirement for dNSHostName attribute to be filled.

We start by creating a computer object. The service account for AGPM should have full control over all group policies in the domain, including those, applied to the root and to the “Domain Controllers” OU. An account which controls group policies for domain controllers, effectively controls those machines. An account which controls domain controllers, controls the domain. Therefore, this account considered a Tier 0 account and, since a OS running on the AGPM server has full access to that account’s processes, it controls the account — that’s why we consider it a Tier 0 object as well.

At Tier 0 administrative workstation run:
New-ADComputer -Name ('{0}{1}' -f $ServerPrefix, $AGPMObjectsId) -Path ('OU={0},OU={1},{2}' -f $TierOU.Tier0.ComputerOUName, $TierOU.Tier0.OUName, $TierOU.Tier0.OUPath)

I use the following naming scheme for server computer objects: <a server prefix + a service identifier + number>. In this blog series I shall use “SRV” as a server prefix ($ServerPrefix).
A service identifier is a string which tells an administrator what services run on this machine. For the purpose of running an AGPM server, simple “AGPM” would be enough ($AGPMObjectsId). When there are several machines serving the same purpose, I just add a numeric identifier as a suffix to their names.

Now, to gMSA creation:
gMSA requires a Key Distribution Services Root Key to be generated and presented in an AD DS domain. Key Distribution Service at domain controllers uses this key to create passwords for gMSA accounts.
By default there is no KDS Root key in the domain. You can check it by running Get-KdsRootKey cmdlet:

To create the key, use Add-KdsRootKey cmdlet. You can run it without any parameters, but by default it creates keys with starting effective time set to 10 days in the future. This is to ensure the key is replicated throughout the directory.
For an immediate result, use -EffectiveImmediately parameter.


-EffectiveImmediately parameter is not supposed for production environments. Be patient when introducing new KDS keys into your infrastructure.

To create a managed service account, use New-ADServiceAccount cmdlet. Note, that you must pass -DNSHostName parameter to it, which is quite unusual for user accounts. In reality it does not have any effect, so you can use here ant DNS name you like. I usually construct it as <service account name> + “.” + <domain DNS name>.
By default, the cmdlet creates new objects in the “CN=Managed Service Accounts” container in the root of an AD DS domain. But in a well-managed infrastructure it would be better to store all objects in appropriate organizational units, that’s why I use -Path parameter, redirecting account creation to the Tier 0 accounts OU.

New-ADServiceAccount -Name $AGPMServiceAccountName -DNSHostName ('{0}.{1}' -f $AGPMServiceAccountName, $Domain.DNSRoot) -Path ('OU={0},OU={1},{2}' -f $TierOU.Tier0.AccountOUName, $TierOU.Tier0.OUName, $TierOU.Tier0.OUPath)


To create a managed service account of an old type, not a group managed one, use -RestrictToSingleComputer parameter

The thing with AGPM is that it was created without having MSA in mind — at the installation phase, when we tell the installer which account use to run the service, it does not understand what is a gMSA. Thankfully, Craig Foster described a proper way to use Managed Service Account with AGPM.
That’s why we must create a temporary user account: it will be used only during the installation phase. The name of the temporary account will be the same, except it does not have the “$” sign at the end.

If you want for a user account to be enabled from the start, you should set it’s password as a part of the creation process.

As I told you earlier, you should NEVER assign permissions to an account – always use a group. For this AGPM installation, we need three groups:

  1. A group which will have a permission to retrieve service account’s password. Its name is stored in $AGPMgMSAGroupName. In my examples this is “ADM-gMSA-User-svc-AGPM”. AGPM server computer account is a member of this group.
  2. A group which is an owner of the AGPM vault. Its name is $AGPMOwnerGroupName, which translates to “ADM-AGPM-Owner”. In this group we will include Domain Admins.
  3. A group through which we will use to add the AGPM service account into another groups.
    Why would we need an additional group for THAT? We do not provide permissions directly to an account, it is normal to add an account into multiple groups, right?
    Well, it depends. As I told you earlier, AGPM does not know nothing about gMSA and therefore we are forced to create a temporary user account just for the installation process. And since we are assigning permissions to that service account, later we should reassign them to the another one. So both of those accounts will have the same permissions. And when you have something recurring, you should automate in one way or another those repetitions. Usage of a group which consolidates accounts to provide them with the same membership in multiple groups is a method to automate group membership change events.
    For this case, this group is not critical and you might very well add the AGPM account into groups directly, but you may find this approach vital for cases with dozens of accounts.

Code for group creation:

As required per documentation, an AGPM service account must be a member of two built-in groups: Backup Operators and Group Policy Creator Owners. As earlier with “Domain Admins”, we target those two by SIDs, because of localization.
Note, that on the first row, I do not request for domain’s SID, but use some very short one. That’s because the first row is for “Backup Operators” group, which is not just a default group, but a built-in one. Built-in groups do not have a domain part in their SIDs but instead use short well-known identifiers.


For more information on default/built-in security identifiers see here, here, and here.

Before proceeding further, prepare the latest AGPM server distributive file (At the moment I am writing this, the latest is “agpm_403_server_amd64.exe”) and an update to it.

Prepare AGPM Server

All right, now to the AGPM server machine:

  1. Assign a new name to the machine and reboot it.
  2. Assign a static IP-address to it.
  3. Enable remote desktop.
  4. Install all latest available updates.
  5. Configure DNS servers at the network interface.
  6. Join the machine into the domain. Reboot it.
  7. Remove SMB1.
  8. Install features required by AGPM server.


RSAT-AD-PowerShell feature is required to install a service account to the machine.

Now we can move to a Tier 0 workstation and continue from there.

We have to copy the distributive to the AGPM server and there are at least two ways to do that:

  1. With SMB.
  2. Using Windows Remote Management protocol (WinRM).

If we want to copy files via SMB, we first should enable it in the firewall, because SMB is not allowed by default.
To execute PowerShell commands at a remote computer you have two built-in channels:

  • The first one is, of course, to RDP there and run PS commands locally.
  • The second one is, again, WinRM.

We will be using WinRM extensively in this series, because RDP consuming more server resources and we would like to minimize that.
Windows Remote Management is Microsoft’s implementation of an open protocol WS-Management (Web Services for Management). It is based on the SOAP standard and all messages there are XML documents, so one can say it is a text-based protocol (easy for troubleshooting, yay!).

There are several built-in tools in Windows to work with the protocol:

  1. winrm command-line utility. Used for WinRM server/client configuration. Allows you to work with WMI on the remote computer through WS-Management instead of RPC.
  2. winrs command-line utility. Look at it as a replacement of the famous Mark Russinovich’s Sysinternals psexec. But, again, it uses WS-Management, not RPC.
  3. Enter-PSSession cmdlet. Gives you an interactive PowerShell session to a remote computer.
  4. Invoke-Command cmdlet. Runs PowerShell code at remote machines non-interactively.

Since in this series we talk about PowerShell, we won’t discuss winrm and winrs.

There are two ways to connect to a remote computer using Enter-PSSession / Invoke-Command cmdlets. The first one is to run them with a -ComputerName parameter. The second one is to use a PSSession object.
A PowerShell session object is an established persistent connection to a WS-Management service which you can reuse multiple times in your scripts. You create one by calling a New-PSSession cmdlet.

Copying the distributive to the AGPM server via SMB

First, let’s see how to open an SMB port (TCP 445) remotely using the Invoke-Command cmdlet:

Another way to achieve that with Enter-PSSession cmdlet:

Next, create a temporary folder on server’s system drive:

Invoke-Command way:

Enter-PSSession way:

SMB way:

If you wish, you can create the folder directly via SMB. But you still have to use Invoke-Command cmdlet to acquire the system drive letter (yes, usually it is “C”, but you can never be completely, 100% sure).

And copy the distributive there:

I assume that you are at a folder with the distributive files. If not, change the path to the files accordingly.

Copying the distributive to the AGPM server via WinRM

To copy files through WinRM, you do not need to open the SMB port, but you must use a PSSession object:

Then you can use that object either in Invoke-Command commands or to connect interactively with the Enter-PSSession cmdlet.

I assume that you are at a folder with the distributive files. If not, change the path to the files accordingly.

Install AGPM Server

To install AGPM server software, we must perform the following:

  1. Allow accounts in the $AGPMServiceAccountsGroupName group to login as a service.
  2. Install the software using “svc-AGPM” account to run the service.
  3. Install the AGPM server update.
  4. Provide the gMSA “svc-AGPM$” with permissions to access and change AGPM server database.
  5. Change the service account to the gMSA “svc-AGPM$”.
  6. Assign SPNs.
  7. Reboot the server machine.

The step #2 has to be run interactively. By some reason, the installation fails when you try to use the Invoke-Command cmdlet. Enter an interactive PowerShell session at the AGPM server computer to execute the commands in this section.

Changing computer security policies from a command-line is tricky, because you have to use a secedit.exe utility, and that tool works with configuration files only — you cannot just pass a single command to it to change one of the policy settings. That’s why I export current security configuration into a file “sec-export.inf” located in a %TEMP% folder, modify it, save to the same location using the name “sec-import.inf” and import that newly created file back into the security configuration.

The following command runs the installer in a silent-mode. Mind, that it does not install any language except English. If you need other languages, set appropriate parameters to “1”.

If you’d like to log the installation process into a file (useful for troubleshooting), define $AGPMInstallationLogName variable and use it with a “/log” option, as shown below:

This strange construction is how you decipher a SecureString back into plain text:

Make sure that the service is up and running. Otherwise — troubleshoot =)

Install the update:
Start-Process -FilePath (Join-Path -Path $env:SystemDrive -ChildPath 'Temp\agpm4.0-Server-KB3127165-x64.exe') -ArgumentList '/quiet' -Wait

Ensure that the service is running after the update:

Stop the service:

Provide to the $AGPMServiceAccountsGroupName group permissions to modify files in “$env:ProgramData\Microsoft\AGPM” folder — that’s where AGPM stores its database:

To use a group-manages service account at a host, you must install it first. Use an Install-ADServiceAccount cmdlet (part of the RSAT-AD-PowerShell Windows feature):
Install-ADServiceAccount -Identity $AGPMServiceAccountName

To check the installation result, you can use a Test-ADServiceAccount cmdlet:

To replace common user account with the gMSA in the service properties, call a “Change” method on the Win32_Service instance:
Invoke-CimMethod -Query 'SELECT Name FROM Win32_Service WHERE Name="AGPM Service"' -MethodName 'Change' -Arguments @{StartName = ('{0}\{1}$' -f $DomainNetBIOSName, $AGPMServiceAccountName)}

Next, set an appropriate SPN (AgpmServer/<server’s FQDN>/<domain name>) at the gMSA object. Remove the SPN from the temporary account.

While Set-ADServiceAccount cmdlet has a -ServicePrincipalNames parameter, we must use Set-ADObject cmdlet, because Set-ADServiceAccount refuses to work with AGPM SPNs (probably because it contains two forward slashes).

AGPM uses a custom TCP port for the client-server communication. Let’s open it:

And, finally, remove AD DS cmdlets from the machine and reboot it:
Uninstall-WindowsFeature -Name 'RSAT-AD-PowerShell' -Restart

Install AGPM Client

Now to the client installation: You should install an AGPM client at a Tier 0 workstation (like WSADMAD), because, as Domain Admins, you will manage all Group Policies in the forest with it.

To manage Group Policies, one needs a “GPMC” Windows feature. We installed it in the previous part, as a part of RSAT.

The second prerequisite for the client is, unfortunately, .NET 3.5:

From the folder with the AGPM client distributive file, run its silent installation:
Start-Process -FilePath '.\agpm_403_client_amd64.exe' -ArgumentList ('/quiet /msicl "PORT=4600 ARCHIVELOCATION={0} ADD_PORT_EXCEPTION=1 BRAZILIAN_PT=0 CHINESE_S=0 CHINESE_T=0 ENGLISH=1 FRENCH=0 GERMAN=0 ITALIAN=0 JAPANESE=0 KOREAN=0 RUSSIAN=0 SPANISH=0"' -f $AGPMServerFQDN)

Import Group Policy objects into AGPM

Let’s review our Group Policy objects:

By default, there are two policy objects pre-installed. To manage them through AGPM, we, first, should import them. To import a GPO, AGPM service should have modify permissions at it. You can easily provide permissions to Group Policy objects with a Set-GPPermission cmdlet. Run the command at the Tier 0 workstation:


You may need to wait several minutes until NTFS permissions will be completely replicated. See KB article 3212430. Do not rush, take a break.

After providing permissions, you can import GPOs into AGPM:
Get-GPO -All | Add-ControlledGpo

From now on copies of our default GPOs are stored under version control. Open Group Policy Management Console and look into “Change Control” section: you should see two group policies there

Remember,that AGPM does not prevent a Domain Admin from modifying a GPO directly, bypassing it. Think of it as of a situation where you have a source code of a web-site in source control, where all the developing takes place, but its copy works on production servers and a person who has access to those servers can modify the code, w/o notifying the CVS.
And, if, at this moment you are thinking about implementing technical measures to restrict your Domain Admins, read this article by Sean Wright, why you should not.


In this part we have installed a control version system for Group Policies in our infrastructure (AGPM). We run it under a Group-Managed Service Account — the most secure way to run services in Windows nowadays. We can manage AGPM from the Tier 0 workstation using both GUI and PowerShell cmdlets.
In the next part we are going to restrict our administrators and I’ll show you how to create and modify Group Policy Objects from a command line.

Stay tuned and Happy Halloween!

Leave a Reply

Your email address will not be published. Required fields are marked *