Previous part — Building Highly-Available Windows Infrastructure: Command-line Style. AD DS. Part 3 — Management Infrastructure
Introduction
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.
Warning
AGPM is NOT a substitute for a proper Active Directory backup.
In this article:
Variables used in the article:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
$Domain = Get-ADDomain $DomainFQDN = $Domain.DNSRoot $DomainNetBIOSName = $Domain.NetBIOSName $Tiers12CombinedOUName = 'Infra' $Tiers12CombinedOUPath = 'OU={0},{1}' -f $Tiers12CombinedOUName, $Domain.DistinguishedName $TierOU = @{ Tier0 = @{ OUName = 'DA' ComputerOUName = 'Servers' GroupOUName = 'Groups' AccountOUName = 'Accounts' OUPath = $Domain.DistinguishedName } Tier1 = @{ OUName = 'Secure' ComputerOUName = 'Servers' GroupOUName = 'Groups' AccountOUName = 'Accounts' OUPath = $Tiers12CombinedOUPath } Tier2 = @{ OUName = 'Normal' ComputerOUName = 'Servers' GroupOUName = 'Groups' AccountOUName = 'Accounts' OUPath = $Tiers12CombinedOUPath } Tier3 = @{ OUName = 'Clients' ComputerOUName = 'Workstations' GroupOUName = 'Groups' AccountOUName = 'Accounts' OUPath = $Domain.DistinguishedName } } $WorkstationPrefix = 'WS' $ServerPrefix = 'SRV' $ServiceAccountPrefix = 'svc' $AdminWSId = 'ADM' $AdminGroupId = 'ADM' $TierIds = @{ Tier0 = 'DA' Tier1 = 'SE' Tier2 = 'NO' Tier3 = 'CL' } $AGPMObjectsId = 'AGPM' $AdminUserNameTemplate = '{0}-admin' $AdminWSNameTemplate = '{0}{1}' -f $WorkstationPrefix, $AdminWSId $gMSAGroupNameTemplate = '{0}-gMSA-User-{1}' -f $AdminGroupId, '{0}' $AGPMgMSAGroupName = $gMSAGroupNameTemplate -f $AGPMServiceAccountName $AGPMOwnerGroupName = '{0}-{1}-Owner' -f $AdminGroupId, $AGPMObjectsId $AGPMServiceAccountName = '{0}-{1}' -f $ServiceAccountPrefix, $AGPMObjectsId $ServiceAccountsGroupTemplate = '{0}-ServiceAccounts-{1}' -f $AdminGroupId, '{0}' $AGPMServiceAccountsGroupName = $ServiceAccountsGroupTemplate -f $AGPMObjectsId $AGPMServerHostName = ('{0}{1}' -f $ServerPrefix, $AGPMObjectsId) $AGPMServerFQDN = ('{0}.{1}' -f $AGPMServerHostName, $Domain.DNSRoot) $AGPMServerIPAddress = '192.0.2.5' # Those are network settings for the AGPM server machine. Adjust them to your infrastructure. $AGPMServerDefaultGateway = '192.0.2.1' $AGPMServerSubnetPrefix = 24 $AGPMServerCDLetter = 'D' # A drive letter of the OS distributive at the AGPM server machine $AGPMClientCDLetter = 'D' # A drive letter of the OS distributive at the AGPM client machine $DC1IPAddress = '192.0.2.3' # Those two are IP addresses of the domain controllers from the part 1. Change accordingly. $DC2IPAddress = '192.0.2.4' |
Prerequisites
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.
Important
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:
1 2 |
PS C:\> Get-KdsRootKey PS C:\> |
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.
1 2 3 4 5 6 |
PS C:\Windows\system32> Add-KdsRootKey -EffectiveImmidiately Guid ---- 503c9d67-3643-0ac0-de67-bd7fc737c87d PS C:\> |
Important
-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)
Note
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.
1 2 3 4 |
PS C:\> $AGPMPassword = Read-Host -AsSecureString ********************************* PS C:\> New-ADUser -Name $AGPMServiceAccountName -UserPrincipalName ('{0}@{1}' -f $AGPMServiceAccountName, $Domain.DNSRoot) -AccountPassword $AGPMPassword -Enabled $true -Path ('OU={0},OU={1},{2}' -f $TierOU.Tier0.AccountOUName, $TierOU.Tier0.OUName, $TierOU.Tier0.OUPath) PS C:\> |
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:
- 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. - 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. - 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:
1 2 3 4 5 6 7 8 9 10 11 |
New-ADGroup -Name $AGPMgMSAGroupName -Path ('OU={0},OU={1},{2}' -f $TierOU.Tier0.GroupOUName, $TierOU.Tier0.OUName, $TierOU.Tier0.OUPath) -GroupCategory Security -GroupScope Universal Set-ADServiceAccount -Identity $AGPMServiceAccountName -PrincipalsAllowedToRetrieveManagedPassword $AGPMgMSAGroupName Add-ADGroupMember -Identity $AGPMgMSAGroupName -Members (Get-ADComputer -Identity $AGPMServerHostName) New-ADGroup -Name $AGPMOwnerGroupName -Path ('OU={0},OU={1},{2}' -f $TierOU.Tier0.GroupOUName, $TierOU.Tier0.OUName, $TierOU.Tier0.OUPath) -GroupCategory Security -GroupScope Universal Add-ADGroupMember -Identity $AGPMOwnerGroupName -Members (Get-ADGroup ('{0}-512' -f ($Domain).DomainSID.Value)) New-ADGroup -Name $AGPMServiceAccountsGroupName -Path ('OU={0},OU={1},{2}' -f $TierOU.Tier0.GroupOUName, $TierOU.Tier0.OUName, $TierOU.Tier0.OUPath) -GroupCategory Security -GroupScope Global # Scope is important here: later we will add this group into another global group — Group Policy Creator Owners Add-ADGroupMember -Identity $AGPMServiceAccountsGroupName -Members (Get-ADServiceAccount -Identity $AGPMServiceAccountName) Add-ADGroupMember -Identity $AGPMServiceAccountsGroupName -Members (Get-ADUser -Identity $AGPMServiceAccountName) |
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.
1 2 |
Add-ADGroupMember -Identity 'S-1-5-32-551' -Members (Get-ADGroup -Identity $AGPMServiceAccountsGroupName) Add-ADGroupMember -Identity (Get-ADGroup ('{0}-520' -f ($Domain).DomainSID.Value)) -Members (Get-ADGroup -Identity $AGPMServiceAccountsGroupName) |
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 for it.
Prepare AGPM Server
All right, now to the AGPM server machine:
- Assign a new name to the machine and reboot it.
- Assign a static IP-address to it.
- Enable remote desktop.
- Install all latest available updates.
- Configure DNS servers at the network interface.
- Join the machine into the domain. Reboot it.
- Remove SMB1.
- Install features required by AGPM server.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#1 Rename-Computer -ComputerName $AGPMServerHostName -Restart #2 New-NetIPAddress -IPAddress $AGPMServerIPAddress -InterfaceAlias 'Ethernet' -DefaultGateway $AGPMServerDefaultGateway -PrefixLength $AGPMServerSubnetPrefix #3 Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\' -Name 'fDenyTSConnections' -Value 0 Set-NetFirewallRule -Name 'RemoteDesktop-UserMode-In-TCP' -Enabled True Set-NetFirewallRule -Name 'RemoteDesktop-UserMode-In-UDP' -Enabled True #5 Set-DnsClientServerAddress -InterfaceAlias 'Ethernet' -ServerAddresses @($DC1IPAddress, $DC2IPAddress) #6 Add-Computer -DomainName $DomainFQDN -Credential (Get-Credential) -Restart #7 Uninstall-WindowsFeature -Name 'FS-SMB1' -Restart #8 Install-WindowsFeature -Name 'NET-Non-HTTP-Activ' -Source ('{0}:\sources\sxs\' -f $AGPMServerCDLetter) Install-WindowsFeature -Name 'GPMC', 'RSAT-AD-PowerShell' |
Note
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:
- With SMB.
- 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:
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.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.Enter-PSSession
cmdlet. Gives you an interactive PowerShell session to a remote computer.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:
1 |
Invoke-Command -ComputerName $AGPMServerFQDN -ScriptBlock {Set-NetFirewallRule -Name 'FPS-SMB-In-TCP' -Enabled True} |
Another way to achieve that with Enter-PSSession cmdlet:
1 2 3 4 5 6 7 |
Windows PowerShell Copyright (C) 2016 Microsoft Corporation. All rights reserved. PS C:\Users\ad-admin> Enter-PSSession -ComputerName $AGPMServerFQDN [srvagpm.lab.exchange12rocks.net]: PS C:\Users\ad-admin\Documents> Set-NetFirewallRule -Name 'FPS-SMB-In-TCP' -Enabled True [srvagpm.lab.exchange12rocks.net]: PS C:\Users\ad-admin\Documents> Exit-PSSession PS C:\> |
Next, create a temporary folder on server’s system drive:
Invoke-Command way:
1 |
Invoke-Command -ComputerName $AGPMServerFQDN -ScriptBlock {New-Item -Name 'Temp' -Path $env:SystemDrive -ItemType Directory} |
Enter-PSSession way:
1 2 3 |
Enter-PSSession -ComputerName $AGPMServerFQDN New-Item -Name 'Temp' -Path $env:SystemDrive -ItemType Directory Exit-PSSession |
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).
1 2 |
$AGPMServerSystemDriveLetter = (Invoke-Command -ComputerName $AGPMServerFQDN -ScriptBlock {$env:SystemDrive})[0] New-Item -Name 'Temp' -Path ('\\{0}\{1}$\' -f $AGPMServerFQDN, $AGPMServerSystemDriveLetter) -ItemType Directory |
And copy the distributive there:
1 2 |
Copy-Item -Path '.\agpm_403_server_amd64.exe' -Destination ('\\{0}\{1}$\Temp\' -f $AGPMServerFQDN, $AGPMServerSystemDriveLetter) Copy-Item -Path '.\AGPM4.0SP1_Server_X64_KB4014009.exe' -Destination ('\\{0}\{1}$\Temp\' -f $AGPMServerFQDN, $AGPMServerSystemDriveLetter) |
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:
1 |
$AGPMPSSession = New-PSSession -ComputerName $AGPMServerFQDN |
Then you can use that object either in Invoke-Command
commands or to connect interactively with the Enter-PSSession
cmdlet.
1 2 3 4 |
$AGPMSystemDrive = (Invoke-Command -Session $AGPMPSSession -ScriptBlock {$env:SystemDrive}) Invoke-Command -Session $AGPMPSSession -ScriptBlock {New-Item -Name 'Temp' -Path $env:SystemDrive -Type Directory} Copy-Item -Path '.\agpm_403_server_amd64.exe' -ToSession $AGPMPSSession -Destination (Join-Path -Path $AGPMSystemDrive -ChildPath 'Temp') Copy-Item -Path '.\AGPM4.0SP1_Server_X64_KB4014009.exe' -ToSession $AGPMPSSession -Destination (Join-Path -Path $AGPMSystemDrive -ChildPath 'Temp') |
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:
- Allow accounts in the
$AGPMServiceAccountsGroupName
group to login as a service. - Install the software using “svc-AGPM” account to run the service.
- Install the AGPM server update.
- Provide the gMSA “svc-AGPM$” with permissions to access and change AGPM server database.
- Change the service account to the gMSA “svc-AGPM$”.
- Assign SPNs.
- 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.
1 2 3 4 5 6 7 8 9 10 11 12 |
$SID = (Get-ADGroup -Identity $AGPMServiceAccountsGroupName -Properties SID).SID.Value $FileExport = Join-Path -Path $env:Temp -ChildPath 'sec-export.inf' $FileImport = Join-Path -Path $env:Temp -ChildPath 'sec-import.inf' $FileDB = Join-Path -Path $env:Temp -ChildPath 'sec-DB.sdb' Start-Process -FilePath 'secedit.exe' -ArgumentList ('/export /cfg {0}' -f $FileExport) -Wait $Lines = Get-Content -Path $FileExport $Lines2 = '' foreach ($Line in $Lines) {if ($Line -like 'SeServiceLogonRight *') {$Line +=",*$SID"}; $Lines2 += "$Line`r`n"} Set-Content -Path $FileImport -Value $Lines2 Start-Process -FilePath 'secedit.exe' -ArgumentList ('/import /db {0} /cfg {1}' -f $FileDB, $FileImport) -Wait Start-Process -FilePath 'secedit.exe' -ArgumentList ('/configure /db {0}' -f $FileDB) -Wait Invoke-GPUpdate -Force |
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”.
1 |
Start-Process -FilePath (Join-Path -Path $env:SystemDrive -ChildPath 'Temp\agpm_403_server_amd64.exe') -ArgumentList ('/quiet /msicl "VAULT_OWNER={0} SVC_USERNAME={1} SVC_PASSWORD={2} USERRUNASSERVICE={1} DSN={0} ADD_PORT_EXCEPTION=0 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 ('{0}\{1}' -f $DomainNetBIOSName, $AGPMOwnerGroupName), ('{0}\{1}' -f $DomainNetBIOSName, $AGPMServiceAccountName), ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AGPMPassword)))) -Wait |
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:
1 2 |
$AGPMInstallationLogName = 'AGPM-Install.log' Start-Process -FilePath (Join-Path -Path $env:SystemDrive -ChildPath 'Temp\agpm_403_server_amd64.exe') -ArgumentList ('/quiet /log {0} /msicl "VAULT_OWNER={1} SVC_USERNAME={2} SVC_PASSWORD={3} USERRUNASSERVICE={2} DSN={1} ADD_PORT_EXCEPTION=0 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 (Join-Path -Path $env:Temp -ChildPath $AGPMInstallationLogName), ('{0}\{1}' -f $DomainNetBIOSName, $AGPMOwnerGroupName), ('{0}\{1}' -f $DomainNetBIOSName, $AGPMServiceAccountName), ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AGPMPassword)))) -Wait |
This strange construction is how you decipher a SecureString back into plain text:
1 2 3 4 5 |
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $AGPMPassword ) ) |
Make sure that the service is up and running. Otherwise — troubleshoot =)
1 2 3 |
PS C:\> (Get-Service -Name 'AGPM Service').Status Running PS C:\> |
Install the update:
Start-Process -FilePath (Join-Path -Path $env:SystemDrive -ChildPath 'Temp\AGPM4.0SP1_Server_X64_KB4014009.exe') -ArgumentList '/quiet' -Wait
Ensure that the service is running after the update:
1 2 3 |
PS C:\> (Get-Service -Name 'AGPM Service').Status Running PS C:\> |
Stop the service:
1 2 3 4 |
PS C:\> Stop-Service -Name 'AGPM Service' PS C:\> (Get-Service -Name 'AGPM Service').Status Stopped PS C:\> |
Provide to the $AGPMServiceAccountsGroupName
group permissions to modify files in “$env:ProgramData\Microsoft\AGPM” folder — that’s where AGPM stores its database:
1 |
Get-Acl -Path (Join-Path -Path $env:ProgramData -ChildPath 'Microsoft\AGPM') | %{ $_.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule ((Get-ADGroup -Identity $AGPMServiceAccountsGroupName -Properties SID).SID, 'Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'))); Set-Acl -Path (Join-Path -Path $env:ProgramData -ChildPath 'Microsoft\AGPM') -AclObject $_} |
To use a group-managed 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:
1 2 3 |
PS C:\> Test-ADServiceAccount -Identity $AGPMServiceAccountName True PS C:\> |
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.
1 2 |
Get-ADServiceAccount -Identity $AGPMServiceAccountName | Set-ADObject -Add @{servicePrincipalName = ('AgpmServer/{0}/{1}' -f $AGPMServerFQDN, $DomainFQDN)} Get-ADUser -Identity $AGPMServiceAccountName | Set-ADUser -ServicePrincipalNames $null |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
PS C:\> New-NetFirewallRule -Name 'AGPM-Service' -DisplayName 'AGPM Service' -Enabled True -Direction Inbound -Action Allow -LocalPort 4600 -Protocol TCP Name : AGPM-Service DisplayName : AGPM Service Description : DisplayGroup : Group : Enabled : True Profile : Any Platform : {} Direction : Inbound Action : Allow EdgeTraversalPolicy : Block LooseSourceMapping : False LocalOnlyMapping : False Owner : PrimaryStatus : OK Status : The rule was parsed successfully from the store. (65536) EnforcementStatus : NotApplicable PolicyStoreSource : PersistentStore PolicyStoreSourceType : Local PS C:\> |
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:
1 2 3 4 5 6 7 8 9 10 11 |
PS C:\> Start-Process -FilePath 'dism' -ArgumentList ('/online /Enable-Feature /FeatureName:NetFx3 /Source:{0}:\sources\sxs' -f $AGPMClientCDLetter) Deployment Image Servicing and Management tool Version: 10.0.14393.0 Image Version: 10.0.14393.0 Enabling feature(s) [==========================100.0%==========================] The operation completed successfully. PS C:\> |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
PS C:\> Get-GPO -All DisplayName : Default Domain Policy DomainName : lab.exchange12rocks.net Owner : LAB\Domain Admins Id : 31b2f340-016d-11d2-945f-00c04fb984f9 GpoStatus : AllSettingsEnabled Description : CreationTime : 10/2/2016 4:59:42 AM ModificationTime : 10/2/2016 4:59:42 AM UserVersion : AD Version: 0, SysVol Version: 0 ComputerVersion : AD Version: 1, SysVol Version: 1 WmiFilter : DisplayName : Default Domain Controllers Policy DomainName : lab.exchange12rocks.net Owner : LAB\Domain Admins Id : 6ac1786c-016f-11d2-945f-00c04fb984f9 GpoStatus : AllSettingsEnabled Description : CreationTime : 10/2/2016 4:59:42 AM ModificationTime : 10/2/2016 4:59:42 AM UserVersion : AD Version: 0, SysVol Version: 0 ComputerVersion : AD Version: 1, SysVol Version: 1 WmiFilter : PS C:\> |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
PS C:\> Set-GPPermission -All -PermissionLevel GpoEditDeleteModifySecurity -TargetName ('{0}\{1}' -f $DomainNetBIOSName, $AGPMServiceAccountsGroupName) -TargetType Group DisplayName : Default Domain Policy DomainName : lab.exchange12rocks.net Owner : LAB\Domain Admins Id : 31b2f340-016d-11d2-945f-00c04fb984f9 GpoStatus : AllSettingsEnabled Description : CreationTime : 9/30/2016 4:45:53 PM ModificationTime : 9/30/2016 4:45:52 PM UserVersion : AD Version: 0, SysVol Version: 0 ComputerVersion : AD Version: 1, SysVol Version: 1 WmiFilter : DisplayName : Default Domain Controllers Policy DomainName : lab.exchange12rocks.net Owner : LAB\Domain Admins Id : 6ac1786c-016f-11d2-945f-00c04fb984f9 GpoStatus : AllSettingsEnabled Description : CreationTime : 9/30/2016 4:45:53 PM ModificationTime : 9/30/2016 4:45:52 PM UserVersion : AD Version: 0, SysVol Version: 0 ComputerVersion : AD Version: 1, SysVol Version: 1 WmiFilter : PS C:\> |
Important
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.
Conclusion
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!
One thought on “Building Highly-Available Windows Infrastructure: Command-line Style. AD DS. Part 4 — AGPM”