Set-NTPConfig.psm1
<#
.SYNOPSIS Configures Windows NTP client with secure, reliable time synchronization. .DESCRIPTION Sets NTP servers, configures polling intervals, and ensures Windows Time service is properly configured. Includes error handling, validation, and geographic detection. .PARAMETER NtpServers Comma-separated list of NTP servers. If not specified, automatically detects region. .PARAMETER Region Geographic region for NTP pool selection. Options: NorthAmerica, Europe, Asia, Oceania, SouthAmerica, Africa, Auto. Default is Auto (detects based on timezone). .PARAMETER SpecialPollInterval Poll interval in seconds. Default is 900 (15 minutes) for workstations, 300 (5 minutes) for servers. Valid range: 64-86400. Lower values increase accuracy but also network traffic. .PARAMETER ServerType System type. Options: Workstation, Server. Adjusts default poll interval accordingly. .PARAMETER Force Skip confirmation prompts. .EXAMPLE .\Set-NTPConfig.ps1 Automatically detects region and configures with appropriate defaults. .EXAMPLE .\Set-NTPConfig.ps1 -Region Europe -SpecialPollInterval 300 Uses European NTP pool servers with 5-minute polling. .EXAMPLE .\Set-NTPConfig.ps1 -NtpServers "time.cloudflare.com,0x9 time.google.com,0x9" -Force Uses custom NTP servers without confirmation. .EXAMPLE .\Set-NTPConfig.ps1 -ServerType Server Configures for server with 5-minute default polling interval. .NOTES Requires Administrator privileges. Author: Enhanced for security and reliability Version: 2.0 #> [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$false)] [string]$NtpServers, [Parameter(Mandatory=$false)] [ValidateSet('NorthAmerica', 'Europe', 'Asia', 'Oceania', 'SouthAmerica', 'Africa', 'Auto')] [string]$Region = 'Auto', [Parameter(Mandatory=$false)] [ValidateRange(64, 86400)] [int]$SpecialPollInterval, [Parameter(Mandatory=$false)] [ValidateSet('Workstation', 'Server')] [string]$ServerType = 'Workstation', [Parameter(Mandatory=$false)] [switch]$Force ) #Requires -RunAsAdministrator # Set strict mode for better error detection Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Write-Log { param( [Parameter(Mandatory=$true)] [string]$Message, [Parameter(Mandatory=$false)] [ValidateSet('Info', 'Warning', 'Error', 'Success')] [string]$Level = 'Info' ) $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $color = switch ($Level) { 'Info' { 'Cyan' } 'Warning' { 'Yellow' } 'Error' { 'Red' } 'Success' { 'Green' } } Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color } function Get-RegionFromTimezone { try { $timezone = Get-TimeZone $timezoneId = $timezone.Id # Map timezone to region switch -Regex ($timezoneId) { 'Pacific|Mountain|Central|Eastern|Alaska|Hawaii|US|Canada|Mexico' { return 'NorthAmerica' } 'Europe|GMT|UTC|London|Paris|Berlin|Rome|Madrid' { return 'Europe' } 'Asia|Tokyo|Seoul|Shanghai|Hong Kong|Singapore|India' { return 'Asia' } 'Australia|New Zealand|Pacific/Auckland' { return 'Oceania' } 'South America|Argentina|Brazil|Chile' { return 'SouthAmerica' } 'Africa|Cairo|Johannesburg' { return 'Africa' } default { Write-Log "Could not detect region from timezone: $timezoneId. Defaulting to NorthAmerica." -Level Warning return 'NorthAmerica' } } } catch { Write-Log "Error detecting timezone: $_. Defaulting to NorthAmerica." -Level Warning return 'NorthAmerica' } } function Get-NtpServersForRegion { param([string]$Region) $ntpPools = @{ 'NorthAmerica' = "0.north-america.pool.ntp.org,0x9 1.north-america.pool.ntp.org,0x9 2.north-america.pool.ntp.org,0x9 3.north-america.pool.ntp.org,0x9" 'Europe' = "0.europe.pool.ntp.org,0x9 1.europe.pool.ntp.org,0x9 2.europe.pool.ntp.org,0x9 3.europe.pool.ntp.org,0x9" 'Asia' = "0.asia.pool.ntp.org,0x9 1.asia.pool.ntp.org,0x9 2.asia.pool.ntp.org,0x9 3.asia.pool.ntp.org,0x9" 'Oceania' = "0.oceania.pool.ntp.org,0x9 1.oceania.pool.ntp.org,0x9 2.oceania.pool.ntp.org,0x9 3.oceania.pool.ntp.org,0x9" 'SouthAmerica' = "0.south-america.pool.ntp.org,0x9 1.south-america.pool.ntp.org,0x9 2.south-america.pool.ntp.org,0x9 3.south-america.pool.ntp.org,0x9" 'Africa' = "0.africa.pool.ntp.org,0x9 1.africa.pool.ntp.org,0x9 2.africa.pool.ntp.org,0x9 3.africa.pool.ntp.org,0x9" } return $ntpPools[$Region] } function Test-RegistryPath { param([string]$Path) try { return Test-Path -Path $Path -ErrorAction Stop } catch { Write-Log "Failed to access registry path: $Path" -Level Error return $false } } function Set-RegistryValue { param( [string]$Path, [string]$Name, [object]$Value, [string]$Type = 'String' ) try { if (-not (Test-RegistryPath -Path $Path)) { Write-Log "Registry path does not exist: $Path" -Level Error throw "Registry path not found" } Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -ErrorAction Stop Write-Log "Set $Name = $Value at $Path" -Level Success return $true } catch { Write-Log "Failed to set registry value $Name at $Path : $_" -Level Error throw } } function Wait-ServiceState { param( [string]$ServiceName, [string]$DesiredState, [int]$TimeoutSeconds = 30 ) $elapsed = 0 $intervalMs = 500 while ($elapsed -lt ($TimeoutSeconds * 1000)) { $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue if ($service.Status -eq $DesiredState) { return $true } Start-Sleep -Milliseconds $intervalMs $elapsed += $intervalMs } return $false } function Test-NtpServer { param([string]$Server) try { Write-Log "Testing connectivity to $Server..." -Level Info $result = w32tm /stripchart /computer:$Server /samples:1 /dataonly 2>&1 if ($LASTEXITCODE -eq 0) { Write-Log "Successfully contacted $Server" -Level Success return $true } else { Write-Log "Could not reach $Server" -Level Warning return $false } } catch { Write-Log "Error testing $Server : $_" -Level Warning return $false } } # Main execution try { Write-Log "=== Windows NTP Configuration Script v2.0 ===" -Level Info Write-Log "Starting NTP configuration..." -Level Info # Determine region if auto-detect if (-not $NtpServers) { if ($Region -eq 'Auto') { $Region = Get-RegionFromTimezone Write-Log "Auto-detected region: $Region" -Level Info } $NtpServers = Get-NtpServersForRegion -Region $Region Write-Log "Using $Region NTP pool servers" -Level Info } else { Write-Log "Using custom NTP servers" -Level Info } # Set default poll interval based on server type if not specified if (-not $PSBoundParameters.ContainsKey('SpecialPollInterval')) { $SpecialPollInterval = if ($ServerType -eq 'Server') { 300 } else { 900 } Write-Log "Using default poll interval for ${ServerType}: $SpecialPollInterval seconds" -Level Info } Write-Log "NTP Servers: $NtpServers" -Level Info Write-Log "Poll Interval: $SpecialPollInterval seconds ($([Math]::Round($SpecialPollInterval/60, 1)) minutes)" -Level Info # Test connectivity to first NTP server $firstServer = ($NtpServers -split ' ')[0] -replace ',0x9', '' Test-NtpServer -Server $firstServer # Backup current configuration Write-Log "`nBacking up current configuration..." -Level Info $w32timeParams = "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" $currentNtpServer = (Get-ItemProperty -Path $w32timeParams -Name "NtpServer" -ErrorAction SilentlyContinue).NtpServer $currentType = (Get-ItemProperty -Path $w32timeParams -Name "Type" -ErrorAction SilentlyContinue).Type if ($currentNtpServer) { Write-Log "Current NTP Server: $currentNtpServer" -Level Info Write-Log "Current Type: $currentType" -Level Info } else { Write-Log "No existing NTP configuration found" -Level Info } # Confirm changes if not forced if (-not $Force -and $PSCmdlet.ShouldProcess("Windows Time Service", "Configure NTP settings")) { Write-Host "`nThis will configure Windows Time with the following settings:" -ForegroundColor Yellow Write-Host " Region: $Region" -ForegroundColor White Write-Host " NTP Servers: $NtpServers" -ForegroundColor White Write-Host " Poll Interval: $SpecialPollInterval seconds" -ForegroundColor White Write-Host "" $continue = Read-Host "Continue with configuration? (Y/N)" if ($continue -ne 'Y') { Write-Log "Configuration cancelled by user." -Level Warning exit 0 } } # 1. Configure W32Time Parameters Write-Log "`nConfiguring W32Time parameters..." -Level Info Set-RegistryValue -Path $w32timeParams -Name "NtpServer" -Value $NtpServers Set-RegistryValue -Path $w32timeParams -Name "Type" -Value "NTP" # 2. Configure NtpClient Provider Write-Log "Configuring NtpClient provider..." -Level Info $ntpClientPath = "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient" Set-RegistryValue -Path $ntpClientPath -Name "SpecialPollInterval" -Value $SpecialPollInterval -Type DWord Set-RegistryValue -Path $ntpClientPath -Name "Enabled" -Value 1 -Type DWord # 3. Configure additional reliability settings Write-Log "Configuring additional time service settings..." -Level Info $configPath = "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" Set-RegistryValue -Path $configPath -Name "MaxPosPhaseCorrection" -Value 3600 -Type DWord Set-RegistryValue -Path $configPath -Name "MaxNegPhaseCorrection" -Value 3600 -Type DWord Set-RegistryValue -Path $configPath -Name "UpdateInterval" -Value 100 -Type DWord # 4. Configure Windows Time Service Write-Log "Configuring Windows Time service..." -Level Info $service = Get-Service -Name w32time -ErrorAction Stop if ($service.StartType -ne 'Automatic') { Set-Service -Name w32time -StartupType Automatic -ErrorAction Stop Write-Log "Set w32time service to Automatic startup" -Level Success } # 5. Restart Service Write-Log "Restarting Windows Time service..." -Level Info if ($service.Status -eq 'Running') { Stop-Service -Name w32time -Force -ErrorAction Stop if (-not (Wait-ServiceState -ServiceName w32time -DesiredState Stopped -TimeoutSeconds 30)) { throw "Service did not stop within timeout period" } Write-Log "Service stopped successfully" -Level Success } Start-Service -Name w32time -ErrorAction Stop if (-not (Wait-ServiceState -ServiceName w32time -DesiredState Running -TimeoutSeconds 30)) { throw "Service did not start within timeout period" } Write-Log "Service started successfully" -Level Success # 6. Apply Configuration and Sync Write-Log "Applying configuration changes..." -Level Info $updateResult = w32tm /config /update 2>&1 if ($LASTEXITCODE -ne 0) { Write-Log "w32tm /config /update returned non-zero exit code: $LASTEXITCODE" -Level Warning Write-Log "Output: $updateResult" -Level Warning } else { Write-Log "Configuration updated successfully" -Level Success } # Wait for service to process configuration Start-Sleep -Seconds 5 Write-Log "Forcing immediate time synchronization..." -Level Info $resyncResult = w32tm /resync /rediscover 2>&1 if ($LASTEXITCODE -eq 0) { Write-Log "Time synchronization initiated successfully" -Level Success } else { Write-Log "Resync returned code $LASTEXITCODE - this may be normal if sync is in progress" -Level Warning } # 7. Verify Configuration Write-Log "`n=== VERIFICATION ===" -Level Info $verifyNtpServer = (Get-ItemProperty -Path $w32timeParams -Name "NtpServer").NtpServer $verifyType = (Get-ItemProperty -Path $w32timeParams -Name "Type").Type $verifyPollInterval = (Get-ItemProperty -Path $ntpClientPath -Name "SpecialPollInterval").SpecialPollInterval $verifyService = Get-Service -Name w32time Write-Host "`nConfigured NTP Servers: " -NoNewline Write-Host $verifyNtpServer -ForegroundColor Green Write-Host "Poll Interval: " -NoNewline Write-Host "$verifyPollInterval seconds ($([Math]::Round($verifyPollInterval/60, 1)) minutes)" -ForegroundColor Green Write-Host "Service Status: " -NoNewline Write-Host "$($verifyService.Status) ($($verifyService.StartType))" -ForegroundColor Green # 8. Display Current Status Write-Log "`n=== CURRENT TIME SYNCHRONIZATION STATUS ===" -Level Info w32tm /query /status Write-Log "`n=== CONFIGURATION COMPLETE ===" -Level Success Write-Host "`nNTP configuration completed successfully!" -ForegroundColor Green Write-Host "Note: It may take a few minutes for initial time synchronization to complete." -ForegroundColor Yellow Write-Host "`nTo verify sync status later, run: " -NoNewline -ForegroundColor Cyan Write-Host "w32tm /query /status" -ForegroundColor White } catch { Write-Log "`n=== FATAL ERROR ===" -Level Error Write-Log "Error: $_" -Level Error Write-Log "Stack Trace: $($_.ScriptStackTrace)" -Level Error # Attempt to restore service if it's stopped $service = Get-Service -Name w32time -ErrorAction SilentlyContinue if ($service -and $service.Status -ne 'Running') { Write-Log "Attempting to restart Windows Time service..." -Level Warning try { Start-Service -Name w32time -ErrorAction Stop Write-Log "Service restarted successfully" -Level Success } catch { Write-Log "Failed to restart service: $_" -Level Error Write-Log "You may need to manually restart the Windows Time service" -Level Error } } exit 1 } finally { Write-Log "`nScript execution completed." -Level Info } |