Public/ntp/Set-NTPClient.ps1
|
#Requires -Version 5.1 #Requires -RunAsAdministrator function Set-NTPClient { <# .SYNOPSIS Configures Windows Time Service (W32Time) to synchronize with specified NTP servers .DESCRIPTION This function configures the Windows Time Service (W32Time) to use external NTP servers for time synchronization. It sets the NTP server list, phase offset tolerance, and polling intervals via registry and w32tm commands. The function ensures the service is running, applies the configuration, restarts the service, forces synchronization, and verifies the final state. Requires local administrator privileges to modify registry and manage the W32Time service. .PARAMETER NtpServers Array of NTP server FQDNs or IP addresses to use for time synchronization. Default: ntp1.ecritel.net, ntp2.ecritel.net .PARAMETER MaxPhaseOffset Maximum allowed phase offset in seconds before the clock is corrected. Valid range: 1 to 3600 seconds. Default: 1 second. .PARAMETER SpecialPollInterval Interval in seconds for special polling operations. Valid range: 1 to 86400 seconds. Default: 300 seconds (5 minutes). .PARAMETER MinPollInterval Minimum poll interval as a power of 2 (2^n seconds). Valid range: 0 to 17. Default: 6 (2^6 = 64 seconds). .PARAMETER MaxPollInterval Maximum poll interval as a power of 2 (2^n seconds). Must be greater than MinPollInterval. Valid range: 0 to 17. Default: 10 (2^10 = 1024 seconds). .EXAMPLE Set-NTPClient Uses default NTP servers (ntp1.ecritel.net, ntp2.ecritel.net) with default settings. .EXAMPLE Set-NTPClient -NtpServers 'time.windows.com', 'pool.ntp.org' -MaxPhaseOffset 5 -Verbose Configures W32Time with custom NTP servers and a 5-second phase offset tolerance, with verbose logging enabled. .EXAMPLE Set-NTPClient -NtpServers 'ntp.example.com' -SpecialPollInterval 600 -MinPollInterval 7 -MaxPollInterval 12 -WhatIf Shows what would happen if the configuration were applied with custom poll intervals. .NOTES Author: Ecritel IT Team Version: 2.0.0 Last Modified: 2026-02-20 Requires: PowerShell 5.1+, Local Administrator rights Permissions: Administrator required to modify registry and manage W32Time service #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [OutputType([void])] param ( [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string[]]$NtpServers = @('ntp1.ecritel.net', 'ntp2.ecritel.net'), [Parameter(Mandatory = $false)] [ValidateRange(1, 3600)] [int]$MaxPhaseOffset = 1, [Parameter(Mandatory = $false)] [ValidateRange(1, 86400)] [int]$SpecialPollInterval = 300, [Parameter(Mandatory = $false)] [ValidateRange(0, 17)] [int]$MinPollInterval = 6, [Parameter(Mandatory = $false)] [ValidateRange(0, 17)] [int]$MaxPollInterval = 10 ) begin { Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' Write-Verbose "[$($MyInvocation.MyCommand)] Starting - PowerShell $($PSVersionTable.PSVersion)" # Validate MaxPollInterval > MinPollInterval at runtime if ($MaxPollInterval -le $MinPollInterval) { throw "MaxPollInterval ($MaxPollInterval) must be greater than MinPollInterval ($MinPollInterval)" } # Registry paths $registryPaths = @{ Config = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config' NtpClient = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient' Parameters = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters' } } process { if (-not $PSCmdlet.ShouldProcess('Windows Time Service (W32Time)', 'Configure NTP synchronization')) { return } try { # Step 1: Ensure W32Time service exists and is running Write-Verbose "[$($MyInvocation.MyCommand)] Checking Windows Time Service status..." $service = Get-Service -Name 'w32time' -ErrorAction SilentlyContinue if (-not $service) { Write-Warning "[$($MyInvocation.MyCommand)] Windows Time Service not found - registering service..." $null = w32tm /register Start-Sleep -Seconds 2 $service = Get-Service -Name 'w32time' -ErrorAction Stop } if ($service.Status -ne 'Running') { Write-Warning "[$($MyInvocation.MyCommand)] Service is stopped - starting..." Start-Service -Name 'w32time' -ErrorAction Stop Write-Information -MessageData '[OK] Windows Time Service started successfully' -InformationAction Continue } else { Write-Verbose "[$($MyInvocation.MyCommand)] Service is already running" } # Step 2: Verify registry paths exist Write-Verbose "[$($MyInvocation.MyCommand)] Verifying registry paths..." foreach ($pathInfo in $registryPaths.GetEnumerator()) { if (-not (Test-Path -Path $pathInfo.Value)) { throw "Registry key not found: $($pathInfo.Value)" } } # Step 3: Build NTP server list with default flags Write-Verbose "[$($MyInvocation.MyCommand)] Building NTP server list..." $serverList = ($NtpServers | ForEach-Object { if ($_ -match '^([^,]+)(,0x[0-9a-fA-F]+)?$') { $serverName = $matches[1] $existingFlag = $matches[2] if ($existingFlag) { $_ } else { "$serverName,0x9" } } else { Write-Warning "[$($MyInvocation.MyCommand)] Unusual server format: $_ - applying default flag" "$_,0x9" } }) -join ' ' Write-Verbose "[$($MyInvocation.MyCommand)] NTP servers: $serverList" Write-Verbose "[$($MyInvocation.MyCommand)] MaxAllowedPhaseOffset: $MaxPhaseOffset second(s)" # Step 4: Apply NTP configuration directly via registry Write-Verbose "[$($MyInvocation.MyCommand)] Writing NTP configuration to registry..." # NtpServer et Type dans la clé Parameters Set-ItemProperty -Path $registryPaths['Parameters'] -Name 'NtpServer' -Value $serverList -Type String -ErrorAction Stop Set-ItemProperty -Path $registryPaths['Parameters'] -Name 'Type' -Value 'NTP' -Type String -ErrorAction Stop # MaxAllowedPhaseOffset dans la clé Config Set-ItemProperty -Path $registryPaths['Config'] -Name 'MaxAllowedPhaseOffset' -Value $MaxPhaseOffset -Type DWord -ErrorAction Stop # Notifier W32Time de relire sa configuration (argument simple, pas de parsing) $null = w32tm.exe /config /update Write-Information -MessageData "[OK] NTP servers configured: $serverList" -InformationAction Continue # Step 5: Set poll intervals in registry Write-Verbose "[$($MyInvocation.MyCommand)] Setting poll intervals in registry..." Set-ItemProperty -Path $registryPaths['NtpClient'] -Name 'SpecialPollInterval' -Value $SpecialPollInterval -ErrorAction Stop Set-ItemProperty -Path $registryPaths['Config'] -Name 'MinPollInterval' -Value $MinPollInterval -ErrorAction Stop Set-ItemProperty -Path $registryPaths['Config'] -Name 'MaxPollInterval' -Value $MaxPollInterval -ErrorAction Stop $minSeconds = [math]::Pow(2, $MinPollInterval) $maxSeconds = [math]::Pow(2, $MaxPollInterval) Write-Information -MessageData "[OK] Poll intervals set: Special=$SpecialPollInterval s, Min=$minSeconds s, Max=$maxSeconds s" -InformationAction Continue # Step 6: Restart W32Time service Write-Verbose "[$($MyInvocation.MyCommand)] Restarting Windows Time Service..." Restart-Service -Name 'w32time' -Force -ErrorAction Stop Start-Sleep -Seconds 2 Write-Information -MessageData '[OK] Windows Time Service restarted successfully' -InformationAction Continue # Step 7: Force synchronization Write-Verbose "[$($MyInvocation.MyCommand)] Forcing immediate NTP synchronization..." $syncJob = Start-Job -ScriptBlock { w32tm /resync /force 2>&1 } $null = Wait-Job -Job $syncJob -Timeout 30 try { $syncOutput = Receive-Job -Job $syncJob -Keep } finally { Remove-Job -Job $syncJob -Force } $successMessageFR = "La commande s'est déroulée correctement." $successMessageEN = 'The command completed successfully.' $isSyncSuccessful = ($syncOutput -match $successMessageFR) -or ($syncOutput -match $successMessageEN) if ($isSyncSuccessful) { Write-Information -MessageData '[OK] Time synchronization completed successfully' -InformationAction Continue } else { Write-Warning "[$($MyInvocation.MyCommand)] Time synchronization may have failed - check output:" $syncOutput | ForEach-Object { Write-Warning "[$($MyInvocation.MyCommand)] $_" } } # Step 8: Verify configuration Start-Sleep -Seconds 3 Write-Verbose "[$($MyInvocation.MyCommand)] Verifying final configuration..." $config = w32tm /query /configuration $configServers = ($config | Select-String -Pattern 'NtpServer:').ToString() -replace '.*NtpServer:\s*', '' -replace '\s*\(.*', '' Write-Information -MessageData "[OK] Configured servers: $configServers" -InformationAction Continue $status = w32tm /query /status /verbose $lastSync = ($status | Select-String -Pattern 'Last Successful Sync Time:').ToString() $source = ($status | Select-String -Pattern 'Source:').ToString() Write-Information -MessageData "[OK] $lastSync" -InformationAction Continue Write-Information -MessageData "[OK] $source" -InformationAction Continue Write-Information -MessageData '[OK] Windows Time Service configuration completed successfully' -InformationAction Continue } catch [System.UnauthorizedAccessException] { Write-Error "[$($MyInvocation.MyCommand)] Access denied - Administrator privileges required: $_" throw } catch [System.InvalidOperationException] { Write-Error "[$($MyInvocation.MyCommand)] Service operation failed: $_" throw } catch { Write-Error "[$($MyInvocation.MyCommand)] Unexpected error during NTP configuration: $_" throw } } end { Write-Verbose "[$($MyInvocation.MyCommand)] Completed" } } |