I am working on a project where I need to update approx 2000 servers to get the time from domain hierarchy (NT5DS). Currently they are getting time from NTP servers. I have created a PS script with 3 functions in it.
function 1: It has global variable where all the list of servers will be stored. It fetches the registry information for NTP configuration on a server. Once function 1 output is saved then it will call function 2.
function 2: This function will take registry key backup on every server under c:\temp. Once backup is done, it will call function 3.
function 3: This function will make registry changes. If changes are already there, it will skip. If changes are required then it will make changes.
I have started writing the advanced functions on my own. This is my 3rd or 4th function. My question is how good this script is. Though I have tested it, it is working as expected.
Can I write a more efficient script to accomplish this type of task? I doubt on the function call in my script,may be not the best approach. So just need some expert guidance on this. Any article/blog or some source would be good to read if you have any or please guide me here as well how can I do it in best programming way.
function Get-NTPServerInfo {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true,
ValueFromPipeline = $true)]
[string[]]$computername
)
BEGIN {Set-Variable -Name server -Value $computername -Force -Scope global
$regpath = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters'
$failedservers = "$env:USERPROFILE\desktop\FailedServers.txt"
}
PROCESS {
foreach ($s in $server) {
try {
$data = @()
Write-Verbose "Getting registry information from $s....."
$properties = Invoke-Command -ScriptBlock {Get-ItemProperty -path $using:regpath |
Select-Object -Property * -ExcludeProperty PSPath,PSParentPath,PSChildName,PSDrive,PSProvider |
Get-Member -MemberType NoteProperty,Property -erroraction "SilentlyContinue"} -ComputerName $s -ErrorAction stop
$properties = $properties | Where-Object {$_.definition -notmatch "byte"}
#enumrate each property getting itsname,value and type
foreach ($property in $properties) {
$value = Invoke-Command -ScriptBlock {(get-itemproperty -path $using:regpath -name $using:property.name).$($using:property.name)} -ComputerName $s
if (-not ($properties)) {
#no item properties were found so create a default entry
$value=$Null
$PropertyItem="(Default)"
$RegType="System.String"
}
else {
#get the registry value type
$regType = $property.Definition.Split()[0]
$PropertyItem = $property.name
}
#create a custom object for each entry and add it the temporary array
$data += New-Object -TypeName PSObject -Property @{
"Path" = $regpath
"Name" = $propertyItem
"Value" = $value
"Type" = $regType
"Computername" = $s
}
} #foreach $property
Write-Verbose "Exporting output to csv....."
$data | Export-Csv -Path $env:USERPROFILE\desktop\reg.csv -NoTypeInformation -Append
Write-Verbose "Calling 'Start-RegBackUp' function to take registry key backup....."
Start-RegBackUp -computername $s
}
catch [System.Management.Automation.RuntimeException] {
Write-Verbose -Message "Unable to connect: $s"
Write-Output "Unable to connect: $s" | Out-File -FilePath $failedservers -Append
}
}
}
END {}
}
function Start-RegBackUp {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true,
ValueFromPipeline = $true)]
[string]$computername
)
BEGIN {$regpath = 'HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters'
$regbackup = "$env:USERPROFILE\desktop\regbackup.csv"
$regbackupfailed = "$env:USERPROFILE\desktop\regbackupfailed.txt"
$date = Get-Date -Format "MM/dd/yyyy HH-mm"
$bkpath = "\\$s\c$\temp\$s-NTP-Export-$date.reg"
}
PROCESS {
try {
if (Test-Path -Path \\$s\c$\temp) {
Write-Verbose -Message "'c:\temp' folder already exists"
}
else {
Write-Verbose -Message "'temp' folder does not exist on $s, creating the folder now....."
New-Item -Path \\$s\c$ -Name temp -ItemType Directory -Force
}
Invoke-Command -ScriptBlock {param ($regkey,$path) reg export $regkey $path} -ComputerName $s -ArgumentList $regpath,$bkpath -ErrorAction Stop
Start-Sleep 5
$data = @()
if (Test-Path -Path $bkpath) {
Write-Verbose "Registry key backup has been successfully taken"
$data = New-Object -TypeName PSCustomObject -Property ([Ordered]@{
'ServerName' = $s
'BackupStatus' = 'Completed'
})
$data | Export-Csv -Path $regbackup -NoTypeInformation -Append
Write-Verbose "Calling 'Set-NTPServer' function to start making changes....."
Start-Sleep 1
Set-NTPServer -computername $s
}
}
catch {
Write-Output "Unable to take registry key backup: $s" | Out-File -FilePath $regbackupfailed -Append
}
}
END {}
}
function Set-NTPServer {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true,
ValueFromPipeline=$true)]
[string]$computername
)
BEGIN {$regpath = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters'
$timesourcecsv = "$env:USERPROFILE\desktop\timesourcecsv.csv"
$pcsntp = 'timeserver1' + ',' + 'timeserver2'
$sourceoutput = @()
$ntpexist = "$env:USERPROFILE\desktop\NTPExist.txt"
$ntpfailed = "$env:USERPROFILE\desktop\NTPFailed.txt"
}
PROCESS {
try {
Write-Verbose -Message "Starting registry changes now....."
Start-Sleep 2
$ntpserver = Invoke-Command -ScriptBlock {param ($regkey) Get-ItemPropertyValue -Path $regkey -Name ntpserver} -ComputerName $s -ArgumentList $regpath -ErrorAction Stop
$ntpserver = $ntpserver -split '[\s+]|,'
if ($ntpserver | Where-Object {$_ -match "timeservers"}) { #Condition: change registry settings if CMS NTP server is configured
Invoke-Command -ScriptBlock {param ($regkey,$ntp) Set-ItemProperty -Path $regkey -Name NTPServer -Value $ntp -Force -PassThru; Start-Sleep 3
w32tm /config /update /syncfromflags:DOMHIER /reliable:yes; Start-Sleep 3
net stop w32time; Start-Sleep 3
net start w32time; Start-Sleep 3
w32tm /resync /nowait; Start-Sleep 30} -ComputerName $s -ArgumentList $regpath,$pcsntp -ErrorAction Stop -Verbose
Write-Verbose -Message "Registry changes are done. Fetching w32tm source....."
Start-Sleep 1
$source = Invoke-Command -ScriptBlock {w32tm /query /source} -ComputerName $s -ErrorAction Stop
$sourceoutput = New-Object -TypeName PSCustomObject -Property ([Ordered]@{
'W32Time Source' = $source
'Server Name' = $s
})
$sourceoutput | Export-Csv -Path $timesourcecsv -Append -NoTypeInformation
Write-Verbose -Message "Exported w32tm source to csv."
}
else {
Write-Output "PCSNG NTP server configuration already in place: $s" | Out-File -FilePath $ntpexist -Append
Write-Verbose -Message "PCSNG NTP server configuration is already in place, no changes required. Fetching w32tm source....."
Start-Sleep 1
$source = Invoke-Command -ScriptBlock {w32tm /query /source} -ComputerName $s -ErrorAction Stop
$sourceoutput = New-Object -TypeName PSCustomObject -Property ([Ordered]@{
'W32Time Source' = $source
'Server Name' = $s
})
$sourceoutput | Export-Csv -Path $timesourcecsv -Append -NoTypeInformation
Write-Verbose -Message "Exported w32tm source to csv."
}
}
catch {
Write-Output "Could not configure NTP settings: $s" | Out-File -FilePath $ntpfailed -Append
Write-Verbose -Message "There is an error while making registry changes. Operation failed"
Start-Sleep 1
}
}
END {}
}
Get-NTPServerInfo -computername server1,server2 -Verbose