Script to Create Group Policy Objects and WMI Filters to Manage the Time Server Hierarchy

by Jeremy Saunders on January 10, 2014

This PowerShell script will create the Time Server GPOs and WMI Filters for the Domain Controllers to ensure your time server hierarchy remains correct for transfer and seizure of the PDC emulator FSMO role holder.

However, before I talk about the script it’s important to provide some background information on the required settings for the Windows Time Service (W32Time), as many tend to get it wrong.

The three (3) important settings are:

  • NTPServer
  • Type
  • AnnounceFlags

NTPServer: Multiple (a pool) of NTP servers can be added by separating them with a space. This setting is used only when Type is set to NTP or AllSync. Additional options can be added to the end of each NTP server in the form of hex codes related to the associating mode it will run in.

  • 0x01 – Special Interval – Specifies to use a special poll interval set by the “SpecialPollInterval” parameter.
  • 0x02 – Use as Fallback Only
  • 0x04 – (Default) Symmetric Active
  • 0x08 – NTP Request in Client Mode

These values can be combined. For Example: 0x09 means SpecialInterval and NTP request in Client mode.

Most environments will typically use option 0x08.

Type: Indicates which peers to accept synchronization from:

  • NoSync – The time service does not synchronize with other sources.
  • NTP – The time service synchronizes from the servers specified in the NtpServer registry entry.
  • NT5DS (Default) – The time service synchronizes from the domain hierarchy.
  • AllSync – The time service uses all the available synchronization mechanisms. It uses the domain hierarchy first and will fall back to the value set for the NTP server if the domain hierarchy is not available.

The PDCe for the forest root domain should be set to NTP. All other DCs should be NT5DS.
The PDCe for the child (non-forest root) domains should be set to AllSync. All other DCs should be NT5DS.

AnnounceFlags: The value define how the DC announces itself as time server:

  • 0x00 – Timeserv_Announce_No, Reliable_Timeserv_Announce_No. The domain controller does not advertise time service.
  • 0x01 – Timeserv_Announce_Yes. The domain controller always advertises time service.
  • 0x02 – Timeserv_Announce_Auto. The domain controller automatically determines whether it should advertise time service.
  • 0x04 – Reliable_Timeserv_Announce_Yes. The domain controller will always advertise reliable time service.
  • 0x08 – Reliable_Timeserv_Announce_Auto. The domain controller automatically determines whether it should advertise reliable time service.

This value combines flags using the bitwise OR operator. For each position in the binary value, if any flag has the value of 1 in the corresponding position, the combined value does as well. For example, the flags 1 and 8, which are 0001 and 1000 in binary, are 1001 when combined with bitwise OR.

AnnounceFlags

0 simply means no announcements, and 1, 2, 4, 8 are the individual bits that decide whether the according function will be announced.
A value of 5 simply is 1+4, so “The domain controller always advertises time service.” and “The domain controller will always advertise reliable time service.”
10 (0xa) means 2+8, so “The domain controller automatically determines whether it should advertise time service.” and “The domain controller automatically determines whether it should advertise reliable time service.”

It’s been reported that even with Announceflags defaulting to 10 the PDCe may not announce itself as a reliable time server under certain scenarios. Therefore setting it to 5 on the PDCe is a considered best practice. All other DCs should be set to 10 as per default. Setting multiple DCs to 5 can cause problems, as explained by Dimitri Janczak in his NtpClient Error 0x800706E1 article.

Whilst Microsoft has published some Windows Time Service Tools and Settings on TechNet, the three (3) listed above are the most important.

Polling Intervals and Clock Corrections Explained:

  • SpecialPollInterval: This value, expressed in seconds, controls how often a manually configured time source is polled when the time source is configured to use a special polling interval. If the SpecialInterval flag is enabled on the NTPServer setting, the client uses the value that is set as the SpecialPollInterval, instead of the MinPollInterval and MaxPollInterval values, to determine how frequently to poll the time source. The default value is 3600 seconds (1 hour).

If you’re going to use the SpecialPollInterval, you may consider setting it to 900 seconds (15 minutes).

  • MinPollInterval: This value, expressed in log base-2 seconds, controls the minimum polling interval that defines the minimum amount of time between polls of a peer. The default value is 6, which is computed as 2 to the power of 6 and equals 64 seconds.
  • MaxPollInterval: This value, expressed in log base-2 seconds, controls the maximum polling interval, which defines the maximum amount of time between polls of a peer. The default value is 10, which is computed as 2 to the power of 10 and equals 1,024 seconds (about 17 minutes). The time service itself is considered unsynchronized after 1.5 times the number of seconds that are specified by this entry have elapsed. NTP specifies that the maximum clock age is 86,400 seconds. Therefore, if the value of this entry is greater than 15 (32,768 seconds), peers will eventually ignore this server.

The MinPollInterval and MaxPollInterval values should not be changed, but be aware that as the actual value in seconds is calculated as a log base-2 number, the time will increase exponentially as you increase these values.

  • MaxNegPhaseCorrection: This value, expressed in seconds, controls the maximum allowable clock correction that can be made in a reverse direction. If a time sample is received that indicates a time in the past (as compared to the client’s local clock) that has a time difference that is greater than the MaxNegPhaseCorrection value, the time sample is discarded. If this happens, the Windows Time source logs an event in the System log of Event Viewer. The default value is 172,800 seconds (48 hours).
  • MaxPosPhaseCorrection: This value, expressed in seconds, controls the maximum allowable clock correction that can be made in a forward direction. If a time sample is received that indicates a time in the future (as compared to the client’s local clock) that has a time difference greater than the MaxPosPhaseCorrection value, the time sample is discarded. If this happens, the Windows Time source logs an event in the System log of Event Viewer. The default value is 172,800 seconds (48 hours).

Some consultants recommend changing the MaxNegPhaseCorrection and MaxPosPhaseCorrection. The Microsoft recommendation is to leave them at their default setting of 17,2800 seconds (48 hours).

Furthermore, it’s also extremely important to ensure your virtual Domain Controllers are correctly configured.

I highly recommend reading the following articles for Hyper-V environments:

The script is fully documented, and is based on a previous script written by Carl Webster.

The following screen shot shows the screen output.

CreateTimeServerGPOs-Output

You can then run the Get-GPO cmdlet to verify that the GPOs were created, User Settings Disabled, and the WMI Filters applied .

TimeServerGPOsandWMIFilters-Verify

The following screen shot shows the GPOs linked to the Domain Controllers OU and WMI Filters .

TimeServerGPOsandWMIFilters

It’s important to note that the script creates the GPOs with the Enabled (VMICTimeProvider) registry preference value disabled as per the following screen shot.

Enabled (VMICTimeProvider) Disabled

You then need to manually set Item-Level targeting before enabling the Enabled value so that this still gets applied to virtual machines ONLY as per best practice. We do it this way because there is no simple way to create/set item-level targeting settings via PowerShell without using a 3rd party product like SDM Software’s GP Automation Engine (GPAE).

  • Right click on the Enabled preference setting
  • Select Properties
  • Select the Common tab
  • Select Item-Level targeting to enable it
  • Select Targeting…
  • Add a Registry Match as per the following settings represented in the screen shot below:
    • Match type: Match value data
    • Value date match type: Substring match
    • Hive: HKEY_LOCAL_MACHINE
    • Key path: SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\VMICTimeProvider
    • Value name: DLLName
    • Value type: REG_EXPAND_SZ
    • Substring: vmictimeprovider.dll

Item-Level Targeting - Registry Match

  • Select OK
  • Select OK
  • Right click on Enabled > All Tasks > Enable

Enabled (VMICTimeProvider) Enabled

You’ll need to do this for all 3 GPOs and then the implementation is complete.

Here is the CreateTimeServerGPOs.ps1 (826 downloads)  script:

#>
  This script will create the Time Server GPOs and WMI Filters for the Domain Controllers
  to ensure your time server hierarchy remains correct for transfer and seizure of the PDC(e)
  emulator FSMO role holder. The policies will apply on the next policy refresh or by forcing
  a group policy refresh.

  WMI Filters are created via the New-ADObject cmdlet in the Active Directory module, which
  makes them of type "Microsoft.ActiveDirectory.Management.ADObject". However, the Group
  Policy module requires that you use an object of type "Microsoft.GroupPolicy.WmiFilter"
  when adding a wmifilter using the New-GPO cmdlet. Therefore there is no default way to use
  the Group Policy PowerShell cmdlets to add WMI Filters to GPOs without a bit or trickery.
  As Carl documented there is a "Group Policy WMI filter cmdlet module" available for download
  from here: http://gallery.technet.microsoft.com/scriptcenter/Group-Policy-WMI-filter-38a188f3
  But if you reverse engineer the code Bin Yi from Microsoft created, you'll see that he has
  simply and cleverly converted a "Microsoft.ActiveDirectory.Management.ADObject" object type
  to a "Microsoft.GroupPolicy.WmiFilter" object type. I didn't want to include the whole module
  for the simple task I needed, so have directly used the ConvertTo-WmiFilter function from the
  GPWmiFilter.psm1 module and tweaked it for my requirements. Many thanks to Bin.

  If your Active Directory is based on Windows 2003 or has been upgraded from Windows 2003, you
  may may have an issue with System Owned Objects. If you receive an error message along the
  lines of "The attribute cannot be modified because it is owned by the system", you'll need to
  set the following registry value:
    Key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\NTDS\Parameters
    Type: REG_DWORD
    Value: Allow System Only Change
    Data: 1

  Disable the Hyper-V time synchronization integration service:
  - The time source of "VM IC Time Synchronization Provider" (vmictimeprovider.dll) is enabled
    on Virtual Machines as part of the Hyper-V Integration Services. The following articles
    explain it in more depth and how it should be configured:
    - Time Sync Recommendations For Virtual DCs On Hyper-V – Change In Recommendations (AGAIN)
      (2013-11-17) Time Sync Recommendations For Virtual DCs On Hyper-V – Change In Recommendations (AGAIN)
    - Time Synchronization in Hyper-V:
      http://blogs.msdn.com/b/virtual_pc_guy/archive/2010/11/19/time-synchronization-in-hyper-v.aspx
    - Hyper V Time Synchronization on a Windows Based Network:
      http://kevingreeneitblog.blogspot.com.au/2011/01/hyper-v-time-synchronization-on-windows.html

  Recommended Default Values for:
  - MaxPosPhaseCorrection: 172800
  - MaxNegPhaseCorrection: 172800

  Recommended Default Values for Domain Controllers:
  - SpecialPollInterval: 3600
    This is only initiated on workgroup servers and the PDCe when a flag of 0x1 or 0×9 is
    specified against any of the manually specified NTP servers.
    References:
    - KB2638243 to understand more about when SpecialPollInterval is used.
    - https://nchrissos.wordpress.com/2013/04/26/configuring-time-on-windows-2008-r2-servers/

  Even after a GPUpdate has occurred and a restart of the Windows Time (W32Time) service you
  may find that the output of a "w32tm /query /source" and "w32tm /query /status" shows that
  it's source is the "Local CMOS Clock". Simply run the "w32tm /resync /rediscover" command
  to force the system to rediscover from its configured sources. This seems to address the
  issue immediately.

  Script Name: CreateTimeServerGPOs.ps1
  Release 1.2
  Written by Jeremy@jhouseconsulting.com 19/10/2015

  Original script was written by Carl Webster:
  - Carl Webster, CTP and independent consultant
  - webster@carlwebster.com
  - @carlwebster on Twitter
  - http://www.CarlWebster.com
  - It can be found here:
    
Creating a Group Policy using Microsoft PowerShell to Configure the Authoritative Time Server
<# #------------------------------------------------------------- param([switch]$whatif) Set-StrictMode -Version 2.0 $VerbosePreference = 'Continue' $WarningPreference = 'Continue' $ErrorPreference = 'Continue' if ($whatif.IsPresent) { $WhatIfPreference = $True Write-Verbose "WhatIf Enabled" } Else { $WhatIfPreference = $False } #------------------------------------------------------------- # Define variables specific to your Active Directory environment # Set this to the NTP Servers the PDCe will sync with $TimeServers = "0.au.pool.ntp.org,0x8 1.au.pool.ntp.org,0x8 2.au.pool.ntp.org,0x8 3.au.pool.ntp.org,0x8" # This is the name of the GPO for the PDCe policy $PDCeGPOName = "+ SERVER Set PDCe Domain Controller as Authoritative Time Server v1.0" # This is the WMI Filter for the PDCe Domain Controller $PDCeWMIFilter = @("PDCe Domain Controller", "Queries for the domain controller that holds the PDC emulator FSMO role", "root\CIMv2", "Select * from Win32_ComputerSystem where DomainRole=5") # This is the name of the GPO for the non-PDCe policy $NonPDCeGPOName = "+ SERVER Set Time Settings on non-PDCe Domain Controllers v1.0" # This is the WMI Filter for the non-PDCe Domain Controllers $NonPDCeWMIFilter = @("Non-PDCe Domain Controllers", "Queries for all domain controllers except for the one that holds the PDC emulator FSMO role", "root\CIMv2", "Select * from Win32_ComputerSystem where DomainRole=4") # This is the name of the GPO for the Domain Member policy $DomainMembersGPOName = "+ COMPUTER Set Time Settings on all Domain Members v1.0" # Set this to True to include the registry value to disable the Virtual Host Time Synchronization provider (VMICTimeProvider) $DisableVirtualHostTimeSynchronization = $True # Set this to true to set the Allow System Only Change registry value $EnableAllowSystemOnlyChange = $True # Set this to the number of seconds you would like to wait for Active Directory replication # to complete before retrying to add the WMI filter to the Group Policy Object (GPO). $SleepTimer = 10 #------------------------------------------------------------- # Import the Active Directory Module Import-Module ActiveDirectory -WarningAction SilentlyContinue if ($Error.Count -eq 0) { Write-Verbose "Successfully loaded Active Directory Powershell's module" } else { Write-Error "Error while loading Active Directory Powershell's module : $Error" exit } # Import the Group Policy Module Import-Module GroupPolicy -WarningAction SilentlyContinue if ($Error.Count -eq 0) { Write-Verbose "Successfully loaded Group Policy Powershell's module" } else { Write-Error "Error while loading Group Policy Powershell's module : $Error" exit } #------------------------------------------------------------- # Get the Current Domain &amp; Forest Information $DomainInfo = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() $DomainName = $DomainInfo.Name $ForestName = $DomainInfo.Forest.Name # Get AD Distinguished Name $DomainDistinguishedName = $DomainInfo.GetDirectoryEntry() | select -ExpandProperty DistinguishedName If ($DomainName -eq $ForestName) { $IsForestRoot = $True } Else { $IsForestRoot = $False } #------------------------------------------------------------- function ConvertTo-WmiFilter([Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject) { # The concept of this function has been taken directly from the GPWmiFilter.psm1 module # written by Bin Yi from Microsoft. I have modified it to allow for the challenges of # Active Directory replication. It will return the WMI filter as an object of type # "Microsoft.GroupPolicy.WmiFilter". $gpDomain = New-Object -Type Microsoft.GroupPolicy.GPDomain $ADObject | ForEach-Object { $path = 'MSFT_SomFilter.Domain="' + $gpDomain.DomainName + '",ID="' + $_.Name + '"' $filter = $NULL try { $filter = $gpDomain.GetWmiFilter($path) } catch { write-Error "The WMI filter could not be found." } if ($filter) { [Guid]$Guid = $_.Name.Substring(1, $_.Name.Length - 2) $filter | Add-Member -MemberType NoteProperty -Name Guid -Value $Guid -PassThru | Add-Member -MemberType NoteProperty -Name Content -Value $_."msWMI-Parm2" -PassThru } else { write-Warning "Waiting $SleepTimer seconds for Active Directory replication to complete." start-sleep -s $SleepTimer write-warning "Trying again to retrieve the WMI filter." ConvertTo-WmiFilter $ADObject } } } #------------------------------------------------------------- function Enable-ADSystemOnlyChange([switch] $disable) { # This function has been taken directly from the GPWmiFilter.psm1 # module written by Bin Yi from Microsoft. $valueData = 1 if ($disable) { $valueData = 0 } $key = Get-Item HKLM:\System\CurrentControlSet\Services\NTDS\Parameters -ErrorAction SilentlyContinue if (!$key) { New-Item HKLM:\System\CurrentControlSet\Services\NTDS\Parameters -ItemType RegistryKey | Out-Null } $kval = Get-ItemProperty HKLM:\System\CurrentControlSet\Services\NTDS\Parameters -Name "Allow System Only Change" -ErrorAction SilentlyContinue if (!$kval) { New-ItemProperty HKLM:\System\CurrentControlSet\Services\NTDS\Parameters -Name "Allow System Only Change" -Value $valueData -PropertyType DWORD | Out-Null } else { Set-ItemProperty HKLM:\System\CurrentControlSet\Services\NTDS\Parameters -Name "Allow System Only Change" -Value $valueData | Out-Null } } #------------------------------------------------------------- Function Create-Policy { param($GPOName,$TargetOU,$NtpServer,$AnnounceFlags,$Type,$MaxPosPhaseCorrection,$MaxNegPhaseCorrection,$SpecialPollInterval,$WMIFilter) If ($WMIFilter -ne "none") { $UseAdministrator = $False If ($UseAdministrator -eq $False) { $msWMIAuthor = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name } Else { $msWMIAuthor = "Administrator@" + [System.DirectoryServices.ActiveDirectory.Domain]::getcurrentdomain().name } # Create WMI Filter $WMIGUID = [string]"{"+([System.Guid]::NewGuid())+"}" $WMIDN = "CN="+$WMIGUID+",CN=SOM,CN=WMIPolicy,CN=System,"+$DomainDistinguishedName $WMICN = $WMIGUID $WMIdistinguishedname = $WMIDN $WMIID = $WMIGUID $now = (Get-Date).ToUniversalTime() $msWMICreationDate = ($now.Year).ToString("0000") + ($now.Month).ToString("00") + ($now.Day).ToString("00") + ($now.Hour).ToString("00") + ($now.Minute).ToString("00") + ($now.Second).ToString("00") + "." + ($now.Millisecond * 1000).ToString("000000") + "-000" $msWMIName = $WMIFilter[0] $msWMIParm1 = $WMIFilter[1] + " " $msWMIParm2 = "1;3;10;" + $WMIFilter[3].Length.ToString() + ";WQL;" + $WMIFilter[2] + ";" + $WMIFilter[3] + ";" # msWMI-Name: The friendly name of the WMI filter # msWMI-Parm1: The description of the WMI filter # msWMI-Parm2: The query and other related data of the WMI filter $Attr = @{"msWMI-Name" = $msWMIName;"msWMI-Parm1" = $msWMIParm1;"msWMI-Parm2" = $msWMIParm2;"msWMI-Author" = $msWMIAuthor;"msWMI-ID"=$WMIID;"instanceType" = 4;"showInAdvancedViewOnly" = "TRUE";"distinguishedname" = $WMIdistinguishedname;"msWMI-ChangeDate" = $msWMICreationDate; "msWMI-CreationDate" = $msWMICreationDate} $WMIPath = ("CN=SOM,CN=WMIPolicy,CN=System,"+$DomainDistinguishedName) $array = @() $SearchRoot = [adsi]("LDAP://CN=SOM,CN=WMIPolicy,CN=System,"+$DomainDistinguishedName) $search = new-object System.DirectoryServices.DirectorySearcher($SearchRoot) $search.filter = "(objectclass=msWMI-Som)" $results = $search.FindAll() ForEach ($result in $results) { $array += $result.properties["mswmi-name"].item(0) } if ($array -notcontains $msWMIName) { write-Verbose "Creating the $msWMIName WMI Filter..." If ($EnableAllowSystemOnlyChange) { Enable-ADSystemOnlyChange } $SOMContainer = [adsi]("LDAP://CN=SOM,CN=WMIPolicy,CN=System,"+$DomainDistinguishedName) $NewWMIFilter = $SOMContainer.create('msWMI-Som',"CN="+$WMIGUID) $NewWMIFilter.put("msWMI-Name",$msWMIName) $NewWMIFilter.put("msWMI-Parm1",$msWMIParm1) $NewWMIFilter.put("msWMI-Parm2",$msWMIParm2) $NewWMIFilter.put("msWMI-Author",$msWMIAuthor) $NewWMIFilter.put("msWMI-ID",$WMIID) $NewWMIFilter.put("instanceType",4) $NewWMIFilter.put("showInAdvancedViewOnly","TRUE") $NewWMIFilter.put("distinguishedname",$WMIdistinguishedname) $NewWMIFilter.put("msWMI-ChangeDate",$msWMICreationDate) $NewWMIFilter.put("msWMI-CreationDate",$msWMICreationDate) If ($WhatIfPreference -eq $False) { $NewWMIFilter.setinfo() } write-Verbose "Waiting $SleepTimer seconds for Active Directory replication to complete." start-sleep -s $SleepTimer } Else { write-Warning "The $msWMIName WMI Filter already exists" } # Get WMI filter &lt;# $SearchRoot = [adsi]("LDAP://CN=SOM,CN=WMIPolicy,CN=System,"+$DomainDistinguishedName) $search = new-object System.DirectoryServices.DirectorySearcher($SearchRoot) $search.filter = "(&amp;(objectclass=msWMI-Som)(mswmi-name=$msWMIName))" $results = $search.FindAll() ForEach ($result in $results) { # To create a WmiFilter object using the ConvertTo-WmiFilter function we need to # first create an object with the following 7 properties: # DistinguishedName, msWMI-Name, msWMI-Parm1, msWMI-Parm2, Name, ObjectClass, ObjectGUID #$WMIFilterADObject = New-Object -TypeName Microsoft.ActiveDirectory.Management.ADObject # There is an Get-ADSIResult function written by Warren Frame that will achieve this: # - https://github.com/RamblingCookieMonster/PowerShell/blob/master/Get-ADSIObject.ps1 # - https://gallery.technet.microsoft.com/scriptcenter/Get-ADSIObject-Portable-ae7f9184 #$WMIFilterADObject | Add-Member -MemberType NoteProperty -Name "DistinguishedName" -value $result.properties["distinguishedname"].item(0) #$WMIFilterADObject | Add-Member -MemberType NoteProperty -Name "msWMI-Name" -value $result.properties["mswmi-name"].item(0) #$WMIFilterADObject | Add-Member -MemberType NoteProperty -Name "msWMI-Parm1" -value $result.properties["mswmi-parm1"].item(0) #$WMIFilterADObject | Add-Member -MemberType NoteProperty -Name "msWMI-Parm2" -value $($result.properties["mswmi-parm2"].item(0)) #$WMIFilterADObject | Add-Member -MemberType NoteProperty -Name "Name" -value $result.properties["name"].item(0) #$WMIFilterADObject | Add-Member -MemberType NoteProperty -Name "ObjectClass" -value "msWMI-Som" ## Convert the ObjectGUID property byte array to a GUID #[GUID]$GUID = $result.properties["ObjectGUID"].item(0) #$WMIFilterADObject | Add-Member -MemberType NoteProperty -Name "ObjectGUID" -value $GUID $WMIFilterADObject = New-Object -TypeName Microsoft.ActiveDirectory.Management.ADObject $WMIFilterADObject.DistinguishedName = $result.properties["distinguishedname"].item(0) $WMIFilterADObject."msWMI-Name" = $result.properties["mswmi-name"].item(0) $WMIFilterADObject."msWMI-Parm1" = $result.properties["mswmi-parm1"].item(0) $WMIFilterADObject."msWMI-Parm2" = ($result.properties["mswmi-parm2"].item(0)).ToString() #$WMIFilterADObject.Name = $result.properties["name"].item(0) $WMIFilterADObject.ObjectClass = "msWMI-Som" # Convert the ObjectGUID property byte array to a GUID [GUID]$GUID = $result.properties["ObjectGUID"].item(0) $WMIFilterADObject.ObjectGUID = $GUID } #&gt; $WMIFilterADObject = Get-ADObject -Filter 'objectClass -eq "msWMI-Som"' -Properties "msWMI-Name","msWMI-Parm1","msWMI-Parm2" | Where {$_."msWMI-Name" -eq "$msWMIName"} #$WMIFilterADObject #$WMIFilterADObject | gm –Force #ConvertTo-WmiFilter $WMIFilterADObject } $ExistingGPO = get-gpo $GPOName -ea "SilentlyContinue" If ($ExistingGPO -eq $NULL) { write-Verbose "Creating the $GPOName Group Policy Object..." If ($WhatIfPreference -eq $False) { $GPO = New-GPO -Name $GPOName write-verbose "Disabling User Settings" $GPO.GpoStatus = "UserSettingsDisabled" } If ($WMIFilter -ne "none") { If ($WhatIfPreference -eq $False) { Write-Verbose "Adding the WMI Filter" $GPO.WmiFilter = ConvertTo-WmiFilter $WMIFilterADObject } } If ($WhatIfPreference -eq $False) { write-verbose "Setting the registry keys in the Preferences section of the new GPO" Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer ` -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config" ` -Type DWord -ValueName "AnnounceFlags" -Value $AnnounceFlags | out-null Write-Verbose "Set AnnounceFlags to a value of $AnnounceFlags" If ($MaxPosPhaseCorrection -ne "default") { Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer ` -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config" ` -Type DWord -ValueName "MaxPosPhaseCorrection" -Value $MaxPosPhaseCorrection | out-null Write-Verbose "Set MaxPosPhaseCorrection to a value of $MaxPosPhaseCorrection" } If ($MaxNegPhaseCorrection -ne "default") { Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer ` -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config" ` -Type DWord -ValueName "MaxNegPhaseCorrection" -Value $MaxNegPhaseCorrection | out-null Write-Verbose "Set MaxNegPhaseCorrection to a value of $MaxNegPhaseCorrection" } If ($SpecialPollInterval -ne "default") { Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer ` -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient" ` -Type DWord -ValueName "SpecialPollInterval" -Value $SpecialPollInterval | out-null Write-Verbose "Set SpecialPollInterval to a value of $SpecialPollInterval" } Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer ` -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" ` -Type String -ValueName "NtpServer" -Value "$NtpServer" | out-null Write-Verbose "Set NtpServer to a value of $NtpServer" Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer ` -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" ` -Type String -ValueName "Type" -Value "$Type" | out-null Write-Verbose "Set Type to a value of $Type" If ($DisableVirtualHostTimeSynchronization) { # Disable the Hyper-V/ESX time synchronization integration service. Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer ` -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\VMICTimeProvider" ` -Type DWord -ValueName "Enabled" -Value 0 -Disable | out-null Write-Verbose "Disabled the VMICTimeProvider" } # Link the new GPO to the specified OU write-Verbose "Linking the $GPOName Group Policy Object to the $TargetOU OU..." New-GPLink -Name $GPOName -Target "$TargetOU" | out-null } } Else { write-Warning "The $GPOName Group Policy Object already exists." If ($WMIFilter -ne "none") { write-Verbose "Adding the $msWMIName WMI Filter..." If ($WhatIfPreference -eq $False) { $ExistingGPO.WmiFilter = ConvertTo-WmiFilter $WMIFilterADObject } write-Verbose "Linking the $GPOName Group Policy Object to the $TargetOU OU..." If ($WhatIfPreference -eq $False) { Try { New-GPLink -Name $GPOName -Target "$TargetOU" -errorAction Stop | out-null } Catch { write-verbose "The GPO is already linked" } } } } write-Verbose "Completed." $ObjectExists = $NULL } #------------------------------------------------------------- If ($IsForestRoot) { $PDCeType = "NTP" } Else { $PDCeType = "AllSync" } $TargetDCOU = "OU=Domain Controllers," + $DomainDistinguishedName # Syntax: # Create-Policy &lt;GPOName&gt; &lt;TargetOU&gt; &lt;NtpServer&gt; &lt;AnnounceFlags&gt; &lt;Type&gt; &lt;MaxPosPhaseCorrection&gt; &lt;MaxNegPhaseCorrection&gt; &lt;SpecialPollInterval&gt; &lt;WMIFilter&gt; Write-Verbose "Creating the WMI Filters and Policies..." Create-Policy "$PDCeGPOName" "$TargetDCOU" "$TimeServers" 5 $PDCeType 172800 172800 3600 $PDCeWMIFilter Create-Policy "$NonPDCeGPOName" "$TargetDCOU" "time.windows.com,0x9" 10 "NT5DS" 172800 172800 "default" $NonPDCeWMIFilter Create-Policy "$DomainMembersGPOName" "$DomainDistinguishedName" "time.windows.com,0x9" 10 "NT5DS" 172800 172800 "default" "none"

References:

Enjoy!

Jeremy Saunders

Jeremy Saunders

Independent Consultant | Contractor | Microsoft & Citrix Specialist | Desktop Virtualization Specialist at J House Consulting
Jeremy is a highly respected, IT Professional, with over 30 years’ experience in the industry. He is an independent IT consultant providing expertise to enterprise, corporate, higher education and government clients. His skill set, high ethical standards, integrity, morals and attention to detail, coupled with his friendly nature and exceptional design and problem solving skills, makes him one of the most highly respected and sought after Microsoft and Citrix technical resources in Australia. His alignment with industry and vendor best practices puts him amongst the leaders of his field.
Jeremy Saunders
Jeremy Saunders
Jeremy Saunders
  • CarlWebster

    Jeremy,

    Thanks for taking the time to do this. I had to comment out the following lines to get the script to work on Server 2012 R2.

    With those lines left in, I got:

    PS C:> .CreateTimeServerGPOs.ps1 -verbose
    Error while loading Active Directory Powershell’s module : Unable to cast object of type ‘System.String[]’ to type ‘System.String’. Unable to cast object of type ‘System.String[]’ to type ‘System.String’.

    With the lines commented out:

    PS C:> .CreateTimeServerGPOs.ps1 -verbose
    Creating the WMI Filters and Policies…

    Creating the PDCe Domain Controller WMI Filter…
    Creating the Set PDCe Domain Controller as Authoritative Time Server v1.0 Group Policy Object…
    Linking the Set PDCe Domain Controller as Authoritative Time Server v1.0 Group Policy Object to the OU=Domain Controllers,DC=webster,DC=local OU…
    Completed.

    Creating the Non-PDCe Domain Controllers WMI Filter…
    Creating the Set Time Settings on non-PDCe Domain Controllers v1.0 Group Policy Object…
    Linking the Set Time Settings on non-PDCe Domain Controllers v1.0 Group Policy Object to the OU=Domain Controllers,DC=webster,DC=local OU…
    Completed.

    PS C:>
    I see the GPOs and WMI filters created and all the settings are correct.
    Thanks
    Webster

    • Hi Webster,

      I tested it on a couple of different domains from 2012 R2 DCs without an issue.

      It’s a strange error. Could it be related to your PowerShell environment?

      Can you add a $error.clear() before the import-module to clear the error variable before we start?

      Did you download the script from the link, or did you copy and paste from the SyntaxHighlighter code?

      The output from the $PSVersionTable on one of the 2012 R2 servers I successfully ran the script from is…

      PSVersion 4.0
      WSManStackVersion 3.0
      SerializationVersion 1.1.0.1
      CLRVersion 4.0.30319.34003
      BuildVersion 6.3.9600.16394
      PSCompatibleVersions {1.0, 2.0, 3.0, 4.0}
      PSRemotingProtocolVersion 2.2

      Cheers,

      Jeremy

      • Phil

        Hi.Great script + article. When applying in a mixed environment (Windows 2003 / 2008 / 2012) the Windows Time service fails to start on Windows 2003 after system restart or manual restart of the service. “Could not start the Windows Time service on Local Computer. Error 2: The system cannot find the file specified.”

        • Hi Phil,

          Yes, I know about this issue. Disable the “Enabled” GPP value and delete the following registry value from all those servers:
          Key: HKLMSYSTEMCurrentControlSetServicesW32TimeTimeProvidersVMICTimeProvider
          Value: Enabled

          I originally wrote this script and tested it in pure virtual environments. The VMICTimeProvider value will prevent the Windows Time Service from starting on physical servers.

          Send me an e-mail for further guidance to jeremy@jhouseconsulting.com

          Cheers,
          Jeremy

  • Pingback: Active Directory Health Check, Audit and Remediation Scripts()

  • Dim

    Your article is missing AnnounceFlage 0x5 = reserved for PDC. I had the issue recently in debugging a nasty ntp error: https://dimitri.janczak.net/2017/02/07/ntpclient-error-0x800706e1/

    • Hi Dimitri,

      Where’s it missing from?

      Re-read the AnnounceFlags section of the document, which concludes with “Therefore setting it to 5 on the PDCe is a considered best practice.”. That whole section shows what each bit means and how the values of 5 and 10 are defined.

      The end of the script also shows that we are passing the value of 5 for the AnnounceFlags parameter to the Create-Policy function for the creation of the “PDCe Domain Controller” policy.

      Cheers,
      Jeremy

      • Dim

        Hi,
        In the description of possible values, you are saying that 5 is the best value for a PDCe, which is right. However if you set 5 for a non PDCe DC, you end up with the infamous error i mentioned previously, although if you take the flags’ description literally it should also work. After a discussion with a MS Engineer, I had this confirmed and could fix the error which resulted from a previous badly written GPO.

        • Sure, but not a badly written GPO from reading my article, or running my script.

          If you say to me that I should change the wording “Therefore setting it to 5 on the PDCe is a considered best practice” to “Therefore setting it to 5 on the PDCe and 10 on the non-PDCe DCs is a considered best practice and will avoid announce issues”, then I can accept that. And would gladly reference your article.

          But you your initial comment was suggesting that I’m wrong, or I haven’t done something right, when this is not the case.

          Cheers,
          Jeremy

          • Dim

            Oh, no, I didn’t meant YOUR script nor YOUR description of AnnounceFlags was wrong, I meant that the MS documentation you’re quoting there is somewhat misleading and can make people think they can play with values on DCs, but that’s not the case, that’s what I said that adding the specific case of 0x5 for AnnounceFlags was missing from the list.
            Cheers

          • What MS article am I quoting? I can’t see what you are seeing.

          • Dim
          • Those documents are not quoted by me. I reference them for various material. I have clarified the description in my article and referenced yours. Perhaps in future you’ll be a bit more polite when commenting on someone else’s blog instead of just saying “Your article is missing…” without any context.

            Cheers,
            Jeremy

Previous post:

Next post: