Get windows time settings from remote servers

Standard

I recently had to deal with some issues with time on our servers and I wanted to get an overview of our environment. To do that I wrote the script in this post which gets the time, timesource (NTP servers) and time zone information from remote windows servers and exports the information to a handy CSV file.

How does the script work?

1. The script loops through all the computers listed in “servers.txt” which much be created and placed in the script directory.
2. Using the function “Test-PortAlive” (I already covered this in a different blog post) the script checks that the ports 135 (rpc) and 445 (smb) are open on the server. This is to prevent the script from timing out and hanging on servers where these ports are blocked. This allows for a quick script run even with many servers.
3. If the ports are open and queries with .net and wmi are possible the script then gets the time, time source and time zone information from the server.
4. When all the information has been retrieved the results are exported to a CSV file in the script directory.

How did I build the script?

I divided the script into three sections Functions, Variables and Script Main to simplify the structure of the script.
The script has four functions which provide the main functionality.
I. Get-TimeSource
This function uses .net to query the remote registry for the NTP server settings.
II. Get-TimeZone
WMI is used to get the time zone configured on the remote server.
III. Get-Time
WMI is used to get the time on the remote server returned as a datetime object.
IV. Test-PortAlive
A function I wrote a while back which checks if given ports are open on a remote server. Here it is used to check if ports 135 and 445 are open on the remote server as this is needed to perform the queries for time configuration information.

In the #script main section I then loop through the servers and for each one execute the above functions to get the info. At then end the information is exported to CSV.

The script itself!

#############################################################################################
##Get-RemoteTime.ps1
##
##Description:		Loops through all the computers listed in "servers.txt" in the script 
#+					directory and for each one gets the timesource, timezone and the time 
#+					using .NET and WMI. Exports the results to a CSV file at the end of the
#+					script run.
##Created by:		Noam Wajnman
##Creation Date:	July 16, 2013
##Updated:			July 31, 2014
##############################################################################################
#FUNCTIONS
function Get-TimeSource {
	#############################################################################################
	##Function:			Get-RemoteTimeSource
	##
	##Description:		Pipeline function. Gets the configured NTP server timesource from the 
	#+					registry of a remote server.
	##
	##Created by:		Noam Wajnman
	##Creation Date:	31 July, 2014	
	##############################################################################################
	[CmdletBinding()]
	[OutputType([System.String])]
	param (
		[Parameter(ValueFromPipeline=$true)]$server
	)
	begin {        
	    $key = "SYSTEM\\CurrentControlSet\\Services\\W32Time\\Parameters" #path/name of the reg key
    }
	process {
		$timesource = '' | select "computer","NTPServer","Type" #Create a custom object properties "computer", "NTPServer" and "Type"		
		$timesource.computer = $server #set the computer property on the custom object		
		$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $server) #connect to the remote registry
		$regKey= $reg.OpenSubKey($key) #get the registry key		
		$str_value1 = $regKey.GetValue("NTPServer")	#get the value of "NTPServer"	
		$value1 = $str_value1 -split ","		
		$timesource.NTPServer = $value1[0] #split the value and set the NTPServer property on the custom object to the first returned timesource.		
		$value2 = $regKey.GetValue("Type") #get the value of "Type"		
		$timesource.Type = $value2 #set the Type property on the custom object		
		$reg.close() #close the remote registry connection when done.
		return $timesource #return the custom object.
	}
}
function Get-TimeZone {
	#############################################################################################
	##Function:			Get-RemoteTimeZone
	##
	##Description:		Pipeline function. Gets the configured time zone from the 
	#+					remote server using WMI.
	##
	##Created by:		Noam Wajnman
	##Creation Date:	31 July, 2014	
	##############################################################################################
	[CmdletBinding()]
	[OutputType([System.String])]
	param (
		$server
	)
	process {
		$timeZone = (gwmi win32_timeZone -ComputerName $server).caption	
		return $timeZone
	}
	
}
function Get-Time {
	#############################################################################################
	##Function:			Get-RemoteTime
	##
	##Description:		Pipeline function. Gets the time from the remote server using WMI.
	##
	##Created by:		Noam Wajnman
	##Creation Date:	31 July, 2014	
	##############################################################################################
	[CmdletBinding()]
	[OutputType([datetime])]
	param (
		$server
	)
	process {
		$remoteOSInfo = gwmi win32_OperatingSystem -computername $server	
		[datetime]$remoteDateTime = $remoteOSInfo.convertToDatetime($remoteOSInfo.LocalDateTime)	
		return $remoteDateTime
	}
}
function Test-PortAlive {
	#############################################################################################
	##Function:			Test-PortAlive
	##
	##Description:		Tests connection on a given server on a given port.
	##
	##Created by:		Noam Wajnman
	##Creation Date:	April 02, 2014	
	##############################################################################################
	[CmdletBinding()]
	[OutputType([System.boolean])]
	param(
		[Parameter(ValueFromPipeline=$true)][System.String[]]$server,
		[int]$port
	)
	$socket = new-object Net.Sockets.TcpClient
	$connect = $socket.BeginConnect($server, $port, $null, $null)
	$NoTimeOut = $connect.AsyncWaitHandle.WaitOne(500, $false)
	if ($NoTimeOut) {
		$socket.EndConnect($connect) | Out-Null
		return $true				
	}
	else {
		return $false
	}
}
#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$servers = gc "$dir\servers.txt"
$CSV = "$dir\ServersTimeInfo.csv"
#SCRIPT MAIN
clear
$TimeInfo = @()
$servers | % {
	$alive_rpc = Test-PortAlive -server $_ -port 135
	$alive_smb = Test-PortAlive -server $_ -port 445
	if ($alive_rpc -and $alive_smb) {
		Write-Host "Getting time info from $_"	
		$RemoteTimeInfo = '' | select "computer","NTPServer","NTP_Type","TimeZone","Time"
		$RemoteTimeInfo.computer = $_
		$timesourceInfo = Get-TimeSource -server $_	
		$RemoteTimeInfo.NTPServer = $timesourceInfo.NTPServer
		$RemoteTimeInfo.NTP_Type = $timesourceInfo.Type
		$RemoteTimeInfo.TimeZone = Get-TimeZone -server $_
		$RemoteTimeInfo.Time = Get-Time -server $_		
		$TimeInfo += $RemoteTimeInfo
	}
	else {
		Write-Host "Error - Couldn't get WMI info from $_"
	}
}
$TimeInfo | Export-Csv $CSV -NoTypeInformation

How do I run the script?

Save the script to your computer as Get-RemoteTime.ps1.
Create servers.txt and place it in the same directory as the script. Populate servers.txt with the names of the servers you wish to get time information from and save. You can now open a powershell prompt, browse to the script directory and execute it. You can also simply open the script in an editor such as powershell ISE or PowerGUI and run it by pressing F5.
That’s it! I hope you find the script useful 🙂

Set VMTools time synchronization mode on all VMs in a cluster

Standard

When changing the time synchronization settings in your environment you may have to also make modifications in your VMWare infrastructure. Among other things it can be necessary to control if the VM should synchronize its time from the host server or not. Normally it’s a manual process where you have to click into the VM settings and go to the Options tab and select VMWare Tools and there check the box or not. This script will do all that for you and set the VMTools host time synchronization to the value you choose, on all VMs in a given cluster. This will save you a lot of time especially if you, like me, manage a VMWare infrastructure with hundreds of VMs.
Although the script is quite short I have divided it into three sections variables, functions and script main to simplify its structure.

Variables

#VARIABLES
$DebugPreference = "continue" #comment out to disable debug info
#Parameters
$vcenter = "some_vcenter_server" #your vcenter server name
$clusterName = "Some_Cluster" #advanced settings will be set on all VMs on this cluster
$TimeSync = $true #script will enable/disable time sync on host on the VMs based on this value. Valid values are $true and $false

Before you run the script you will need to fill out the parameters in the variables section. $clustername is the name of the cluster on which you want to the change the settings for your VMs. All VMs running on the cluster will have their VMTools host time sync value set to what you specify in $TimeSync.

Functions

#FUNCTIONS
function ConnectToVcenter {
	param(
		$vcenter
	)
	#Load snap-in and connect to vCenter
	if (-not (Get-PSSnapin -Name VMware.VimAutomation.Core)) {
		Add-PSSnapin VMware.VimAutomation.Core
	}
	if ($global:DefaultVIServers.Count -lt 1) {
		Connect-VIServer $vCenter
	}
}
function Set-SyncValue {
	[CmdletBinding()]
	[OutputType([System.String])]
	param(
		[Parameter(ValueFromPipeline=$true)]$VM,
		[bool]$SyncValue
	)
	begin {
		Write-Debug "Creating configuration specification object"
		$ConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec 
		$ConfigSpec.tools = New-Object VMware.Vim.ToolsConfigInfo 
		$ConfigSpec.tools.syncTimeWithHost = $SyncValue
	}
	process {
		#configure the VM
		Write-Debug "Setting VMTools time sync to $SyncValue on $($VM.Name)"
		$View = get-view -viewtype virtualmachine -Filter @{'name'=$VM.Name} 
		$View.ReconfigVM_task($ConfigSpec)
		sleep 3 #wait for the configuration to complete
		#Check if the VM was configured as desired.
		$AfterView = get-view -viewtype virtualmachine -Filter @{'name'=$VM.Name} 
		[bool]$result = [System.Convert]::ToBoolean($AfterView.Config.Tools.syncTimeWithHost)		
		if ($result -eq $SyncValue) {
			Write-Debug "Successfully set the Tools.syncTimeWithHost on $VM to $SyncValue"
		}
		else {
			Write-Debug "Error - $VM - Couldn't set the Tools.syncTimeWithHost to the desired value of $SyncValue"
		}
	}
	end {
		Write-Debug "Finished setting time sync values on VMs."
	}
}

The script uses two functions which I will go over now.
1. Function ConnectToVcenter
This function takes one parameter $vcenter which is the name of the vcenter server. If not already done the function adds the VMWare snap-in for powershell and then connects to vcenter to make ready for the commands we will run after.
2. Function Set-SyncValue
Set-SyncValue takes two parameters $VM and $SyncValue. $VM can be passed from the pipeline which makes it easy to use in combination with other VMWare commands. The function works by first creating a configuration specification object with the value given in $SyncValue. After this it loops though the given VMs and reconfigures them accordingly. After the command to configure a VM has been sent, the script waits 3 seconds and then checks if the setting was updated to the value you wanted.

Script Main

#SCRIPT MAIN
ConnectToVcenter -vcenter $vcenter
Get-Cluster $clusterName | Get-VM | Set-SyncValue -SyncValue $TimeSync 

The script main is very short and simple. First I connect to vcenter using the function ConnectToVcenter. The actual work is then done in a one liner where I simply pipe all the VMs from the Get-Cluster and Get-VM cmdlets to the Set-SyncValue function.

That’s it. I hope you find the script useful. I have copied the full version of the script in below.

#####################################################################################################
##Script:			Set-VMTools_TimeSync.ps1
##
##Description:		enables/disables the VMTools time sync mode on all VMs in a given cluster.
##Created by:		Noam Wajnman
##Credits:			Part of this script was based on this article: 
#+					https://psvmware.wordpress.com/tag/disable-vmware-tools-time-sync-using-powercli/
##Creation Date:	April 28, 2014
#####################################################################################################
#VARIABLES
$DebugPreference = "continue" #comment out to disable debug info
#Parameters
$vcenter = "some_vcenter_server"
$clusterName = "Some_Cluster" #advanced settings will be set on all VMs on this cluster
$TimeSync = $true #script will enable/disable time sync on host on the VMs based on this value. Valid values are $true and $false
#FUNCTIONS
function ConnectToVcenter {
	param(
		$vcenter
	)
	#Load snap-in and connect to vCenter
	if (-not (Get-PSSnapin -Name VMware.VimAutomation.Core)) {
		Add-PSSnapin VMware.VimAutomation.Core
	}
	if ($global:DefaultVIServers.Count -lt 1) {
		Connect-VIServer $vCenter
	}
}
function Set-SyncValue {
	[CmdletBinding()]
	[OutputType([System.String])]
	param(
		[Parameter(ValueFromPipeline=$true)]$VM,
		[bool]$SyncValue
	)
	begin {
		Write-Debug "Creating configuration specification object"
		$ConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec 
		$ConfigSpec.tools = New-Object VMware.Vim.ToolsConfigInfo 
		$ConfigSpec.tools.syncTimeWithHost = $SyncValue
	}
	process {
		#configure the VM
		Write-Debug "Setting VMTools time sync to $SyncValue on $($VM.Name)"
		$View = get-view -viewtype virtualmachine -Filter @{'name'=$VM.Name} 
		$View.ReconfigVM_task($ConfigSpec)
		sleep 3 #wait for the configuration to complete
		#Check if the VM was configured as desired.
		$AfterView = get-view -viewtype virtualmachine -Filter @{'name'=$VM.Name} 
		[bool]$result = [System.Convert]::ToBoolean($AfterView.Config.Tools.syncTimeWithHost)		
		if ($result -eq $SyncValue) {
			Write-Debug "Successfully set the Tools.syncTimeWithHost on $VM to $SyncValue"
		}
		else {
			Write-Debug "Error - $VM - Couldn't set the Tools.syncTimeWithHost to the desired value of $SyncValue"
		}
	}
	end {
		Write-Debug "Finished setting time sync values on VMs."
	}
}
#SCRIPT MAIN
ConnectToVcenter -vcenter $vcenter
Get-Cluster $clusterName | Get-VM | Set-SyncValue -SyncValue $TimeSync