0

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
halfer
  • 19,824
  • 17
  • 99
  • 186
JPS
  • 23
  • 3
  • Any particular reason you aren't using Group Policy for this? – Mathias R. Jessen Jun 19 '23 at 14:39
  • Stackoverflow is not meant for code reviews, instead you might use [StackExchange Code Review](https://codereview.stackexchange.com/). Anyways, for a starter: you might want to read [about pipelines](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pipelines), Meaning rather thaan calling each of the functions from another function Consider to create an actual PowerShell Pipeline: `Get-NTPServerInfo |Start-RegBackUp |Set-NTPServer` – iRon Jun 19 '23 at 15:06
  • 1
    Also, you might read [PowerShell performance considerations](https://learn.microsoft.com/powershell/scripting/dev-cross-plat/performance/script-authoring-considerations), meaning [avoid using the increase assignment operator (`+=`) to create a collection](https://stackoverflow.com/a/60708579/1701026) and avoid embedding cmdlets (as: `$data | Export-Csv -Path...`), see also: [Mastering the (steppable) pipeline](https://devblogs.microsoft.com/powershell-community/mastering-the-steppable-pipeline/). – iRon Jun 19 '23 at 15:07
  • Thanks @iRon, will go through these articles and take care of suggestions. – JPS Jun 21 '23 at 16:17
  • As interesting as code review questions are, they are off-topic on Stack Overflow. Consider posting it on the Code Review website after reading their on-topic guidance. – halfer Jun 30 '23 at 22:28

0 Answers0