Public/Create-TimeSourceGPO.ps1

Function Create-TimeSourceGPO {
   $TimeServers = "0.pool.ntp.org,0x8 1.pool.ntp.org,0x8 2.pool.ntp.org,0x8 3.pool.ntp.org,0x8"

   #========================================================
   # Reset powershell session
   #========================================================

   Clear-Host
   $error.Clear()

   #####################################################################################################################################################

   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
   # Function: Load-Module
   # Purpose : Import Powershell module with validation
   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

   Function Report-Status {
      Param(
         [parameter(Mandatory = $true)][String]$Msg,
         [parameter(Mandatory = $true)][INT]$Lvl,
         [parameter(Mandatory = $true)][String]$Color
      )
      Switch ($Lvl) {
         0 { Write-Host -Foreground $Color $Msg }
         1 { Write-Host -Foreground $Color " -" $Msg }
         2 { Write-Host -Foreground $Color " *" $Msg }
      }
   }

   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
   # Function: Load-Module
   # Purpose : Import Powershell module with validation
   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

   Function Load-Module {
      Param(
         [parameter(Mandatory = $true)]
         [String]
         $ModuleName,
         [parameter(Mandatory = $true)]
         [String]
         $Description
      )

      If ( ! (Get-Module $ModuleName)) {
         Report-Status "Importing $ModuleName module" 1 Green
         Import-Module $ModuleName -WarningAction SilentlyContinue
      }

      if ($Error.Count -ne 0) { 
         Report-Status "Error while loading $Description module : $Error`n" 1 Red
         exit
      }
   }

   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
   # Function: Convert-IpAddressToMaskLength
   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

   function Convert-IpAddressToMaskLength([string] $dottedIpAddressString) {
      $result = 0; 
      # ensure we have a valid IP address
      [IPAddress] $ip = $dottedIpAddressString;
      $octets = $ip.IPAddressToString.Split('.');
      foreach ($octet in $octets) {
         while (0 -ne $octet) {
            $octet = ($octet -shl 1) -band [byte]::MaxValue
            $result++; 
         }
      }
      return $result;
   }

   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
   # Function: ConvertTo-WMIFilter
   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=


   function ConvertTo-WmiFilter([Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject) {
      $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 {
            Report-Status "The WMI filter could not be found." 1 Red
         }
         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 {
            Report-Status "Waiting $SleepTimer seconds for Active Directory replication to complete." 1 Yellow
            Start-Sleep -s $SleepTimer
            Report-Status "Trying again to retrieve the WMI filter." 1 Yellow
            ConvertTo-WmiFilter $ADObject
         }
      }
   }

   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
   # Function: Create-TimeSyncGPO
   # Purpose : Create Custom GPO for NTP configuration
   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

   Function Create-TimeSyncGPO {
      param($GPOName, $NtpServer, $AnnounceFlags, $Type, $WMIFilter)

      If ($AllowSystemOnlyChange) { New-ItemProperty "HKLM:\System\CurrentControlSet\Services\NTDS\Parameters" -name "Allow System Only Change" -value 1 -propertyType dword -EA 0 }

      $UseAdministrator = $False

      If ($UseAdministrator -eq $False) {
         $msWMIAuthor = (Get-ADUser $env:USERNAME).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," + $defaultNC
      $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," + $defaultNC) 
      $ExistingWMIFilters = Get-ADObject -Filter 'objectClass -eq "msWMI-Som"' -Properties "msWMI-Name", "msWMI-Parm1", "msWMI-Parm2"
      $array = @()

      If ($ExistingWMIFilters -ne $NULL) {
         foreach ($ExistingWMIFilter in $ExistingWMIFilters) { $array += $ExistingWMIFilter."msWMI-Name" }
      } 
      Else {
         $array += "no filters"
      }

      Report-Status "Creating the $msWMIName WMI Filter" 1 Green
      if ($array -notcontains $msWMIName) {
         $WMIFilterADObject = New-ADObject -name $WMICN -type "msWMI-Som" -Path $WMIPath -OtherAttributes $Attr
      } 
      Else {
         Report-Status "The $msWMIName WMI Filter already exists." 2 Yellow
      }
    
      $WMIFilterADObject = $NULL

      # Get WMI filter
      $WMIFilterADObject = Get-ADObject -Filter 'objectClass -eq "msWMI-Som"' -Properties "msWMI-Name", "msWMI-Parm1", "msWMI-Parm2" | Where-Object { $_."msWMI-Name" -eq "$msWMIName" }

      Report-Status "Creating the $GPOName Group Policy Object" 1 Green
      $ExistingGPO = get-gpo $GPOName -ea "SilentlyContinue"   
  
      If ($ExistingGPO -eq $NULL) {

         # Create new GPO shell
         $GPO = New-GPO -Name $GPOName

         # Disable User Settings
         $GPO.GpoStatus = "UserSettingsDisabled"

         # Add the WMI Filter
         $GPO.WmiFilter = ConvertTo-WmiFilter $WMIFilterADObject

         # Set the three 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
         Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" -Type String -ValueName "NtpServer" -Value "$NtpServer" | Out-Null
         Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" -Type String -ValueName "Type" -Value "$Type" | Out-Null

         # Disable the Hyper-V time synchronization integration service.
         If ($DisableHyperVTimeSynchronization) {
            Set-GPPrefRegistryValue -Name $GPOName -Action Update -Context Computer -Key "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\VMICTimeProvider" -Type DWord -ValueName "Enabled" -Value 0 | Out-Null
         }

         # Link the new GPO to the Domain Controllers OU
         Report-Status "Linking the $GPOName Group Policy Object to $DomainControllersOU" 1 Green
         $Result = New-GPLink -Name $GPOName -Target "$DomainControllersOU"
      } 
      Else {
         Report-Status "The $GPOName Group Policy Object already exists." 2 Yellow
         Report-Status "Applying the $msWMIName WMI Filter" 1 Green
         $ExistingGPO.WmiFilter = ConvertTo-WmiFilter $WMIFilterADObject
      }
      $ObjectExists = $NULL
   }

   #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

   #========================================================
   # Import the required modules
   #========================================================

   Report-Status "Loading required modules" 0 cyan
   Load-Module "ActiveDirectory" "Active Directory"
   Load-Module "GroupPolicy" "Group Policy"

   #========================================================
   # Global Variables
   #========================================================

   # Set source path for all configuration files
   $WorkFolderPath = $PSScriptRoot

   # Set Active Directory Domain FQDN and NetBIOS names
   $ADDomain = Get-ADDomain
   $ADNetBIOS = $ADDomain.NetBIOSName
   $ADDNSRoot = $ADDomain.DNSRoot

   #========================================================
   # Time Source Variables
   #========================================================

   # This is the name of the GPO for the PDCe policy
   $PDCeGPOName = "SYST-DomainControllers_TimeSource-PDCe"

   # This is the WMI Filter for the PDCe Domain Controller
   $PDCeWMIFilter = @("Domain Controller (PDCe)", "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 = "SYST-DomainControllers_TimeSource"

   # This is the WMI Filter for the non-PDCe Domain Controllers
   $NonPDCeWMIFilter = @("Domain Controllers (Non-PDCe)", "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")

   # Set this to True to include the registry value to disable the Hyper-V Time Synchronization
   $DisableHyperVTimeSynchronization = $True

   # Set this to True if you need to set the "Allow System Only Change" value.
   $AllowSystemOnlyChange = $False

   # 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

   # Set Target OU for Domain Controllers
   $defaultNC = ([ADSI]"LDAP://RootDSE").defaultNamingContext.Value
   $DomainControllersOU = "OU=Domain Controllers," + $defaultNC


   #========================================================
   # Configure GPOs
   #========================================================

   # Create Domain Controller GPOs and WMI Filters for Time Synchronisation
   Report-Status "Creating Time Sync GPOs for Domain Controllers" 0 cyan
   Create-TimeSyncGPO "$PDCeGPOName" "$TimeServers" 5 "NTP" $PDCeWMIFilter
   Create-TimeSyncGPO "$NonPDCeGPOName" "" 10 "NT5DS" $NonPDCeWMIFilter
}