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 🙂

Advertisements

Get HBA device info from remote servers and export to CSV

Standard

If you work with SAN storage and fibre channel adapters it can be very useful to get an overview of all your HBAs (host bus adapters) on your servers. This script uses WMI to get HBA info like WWN, driver, version, model etc. from remote servers and then export it to a CSV file. You will then have a consolidated view of all your HBA devices with detailed information about them.
You will need to create the file servers.txt in the script directory and enter the names (one per line) of the servers you want to get the info from.
I have divided this script into three sections variables, functions and script main. I will now briefly explain how I created each section.

Variables

#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath #path to the directory in which the script is run.
$servers = @(gc "$dir\servers.txt") #create servers.txt in the script directory and enter the names of servers you wish to query for HBA info.
$CSV = "$dir\HBAs.csv" #results will be exported to a csv file with this path

$servers is an array of server names populated by running Get-Content on the file servers.txt. $CSV is the path of the CSV file which will be created at the end of the script run. Both objects $servers and $CSV are defined using the object $dir which always points to the directory which the script was started in. This is practical as it allows me to copy and move the script around without having to change any paths.

Functions

1. Function Get-HBAInfo

#FUNCTIONS
function Get-HBAInfo {
	[CmdletBinding()]
	[OutputType([System.String])]
	param(  
		[parameter(ValueFromPipeline = $true)]$server  
	)	
	process {
		$WMI = Get-WmiObject -class MSFC_FCAdapterHBAAttributes -namespace "root\WMI" -computername $server		
		$WMI | % {
			$HBA = "" | select "server","WWN","DriverName","DriverVersion","FirmwareVersion","Model","ModelDescription"
			$HBA.server = $server
			$HBA.WWN = (($_.NodeWWN) | % {"{0:x}" -f $_}) -join ":"				
			$HBA.DriverName       = $_.DriverName  
			$HBA.DriverVersion    = $_.DriverVersion  
			$HBA.FirmwareVersion  = $_.FirmwareVersion  
			$HBA.Model            = $_.Model  
			$HBA.ModelDescription = $_.ModelDescription
			$HBA
		}			
	}	
}

This function takes a single parameter $server and runs a WMI query on it to get the HBA information. For each HBA device found on the server A custom object called $HBA is created and returned. The function can take input from the pipeline which is practical as you can simply pass the server name to the function using another script or cmdlet if you want.

Script Main

#SCRIPT MAIN
clear
$HBAs = @($servers | Get-HBAInfo)
$HBAs | Export-Csv $CSV -NoTypeInformation -Force

The script main is very simple consisting of only two lines. First I use the Get-HBAInfo function to get the HBA information from the given servers. Then, in the second line, the results are exported to CSV.

I have copied in the full script below. I hope you find it useful. Enjoy!!

################################################################################################
##Script:			Get-HBAInfo.ps1
##
##Description:		Gets information about HBAs on the given servers using WMI and exports it to 
#+					CSV. Remember to create the file servers.txt in the script directory and
#+					enter the names (one per line) of the servers you want to get the info from.
##Created by:		Noam Wajnman
##Creation Date:	February 28, 2013
##Updated:			April 08, 2014
################################################################################################
#FUNCTIONS
function Get-HBAInfo {
	[CmdletBinding()]
	[OutputType([System.String])]
	param(  
		[parameter(ValueFromPipeline = $true)]$server  
	)	
	process {
		$WMI = Get-WmiObject -class MSFC_FCAdapterHBAAttributes -namespace "root\WMI" -computername $server		
		$WMI | % {
			$HBA = "" | select "server","WWN","DriverName","DriverVersion","FirmwareVersion","Model","ModelDescription"
			$HBA.server = $server
			$HBA.WWN = (($_.NodeWWN) | % {"{0:x}" -f $_}) -join ":"				
			$HBA.DriverName       = $_.DriverName  
			$HBA.DriverVersion    = $_.DriverVersion  
			$HBA.FirmwareVersion  = $_.FirmwareVersion  
			$HBA.Model            = $_.Model  
			$HBA.ModelDescription = $_.ModelDescription
			$HBA
		}			
	}	
}
#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath #path to the directory in which the script is run.
$servers = @(gc "$dir\servers.txt") #create servers.txt in the script directory and enter the names of servers you wish to query for HBA info.
$CSV = "$dir\HBAs.csv" #results will be exported to a csv file with this path
#SCRIPT MAIN
clear
$HBAs = @($servers | Get-HBAInfo)
$HBAs | Export-Csv $CSV -NoTypeInformation -Force

Powershell – Get DNS A records and export to CSV

Standard

When managing and cleaning up your IP addresses/ranges it can be very useful to get some lists of the records you have in your DNS zones. Here’s a simple script which gets the IP addresses/hostnames of the given DNS zones and exports the results to CSV.
The script is split into two parts Variables and script main. I will walk through and explain both parts below.

Variables

#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$CSV = "$dir\DNS_A_records.csv"
#parameters
$DNSServer = "Some_DNS_server"
$Zone1 = "myzone.local"
$Zone2 = "me.myzone.local"

I use the $dir object in the paths of my scripts as this object always points to the directory which the script has been run from. This is practical as it allows you to move/copy the script around without having to change any paths. $CSV is the path to the CSV file which will be created at the end of the script run.
Remember to fill out the parameters section before you run the script! $DNSServer is the name of your DNS server. $Zone1, $Zone2… etc are the names of the DNS zones which you want to get A records from. If you have more than 2 zones to get A records from then just add more objects to match the number of zones you need and add the zone names.

Script Main

#SCRIPT MAIN
clear
$DNS_Zones = @()
$DNS_Zones += $Zone1
$DNS_Zones += $Zone2
$hosts = @()
$DNS_Zones | % {
	$zone = $_
	Write-Host "Getting DNS A records from $zone"	
	$DNS_A_records = @(Get-WmiObject -Class MicrosoftDNS_AType -NameSpace Root\MicrosoftDNS -ComputerName $DNSServer -Filter "ContainerName = `'$zone`'")
	$DNS_A_records | % {
		$hostA = "" | select "hostname","IPAddress"
		$hostA.hostname = $_.OwnerName
		$hostA.IPAddress = $_.IPAddress
		$hosts += $hostA
	}
}
$hosts = $hosts | Sort-Object @{Expression={[Version]$_.IPAddress}}
$hosts | Export-Csv $CSV -NoTypeInformation -Force

In the script main I first create the array $DNS_Zones which will hold the different zone names. Then the zone names ($Zone1, $Zone2… etc.) are added to the array. If you created more than two zone objects in the variables section you must add them here too.
I now create the $hosts array which will hold the records we will wish export later. The next thing that happens is that we loop through the $DNS_Zones array. For each DNS Zone we get all A records using WMI and for each of these, a custom object with the properties hostname and IPAddress is created and added to the $hosts array.
I then sort the $hosts array by IP Address using the following code:
$hosts | Sort-Object @{Expression={[Version]$_.IPAddress}}
The @{Expression} argument allows you to add the [version] type declaration on the IPAddress property on the array elements/objects which in turn enables you to easily sort the IPs.
Finally the $hosts array is exported to CSV using the great Export-CSV powershell cmdlet.
I have copied in the full script below.I hope you find it useful. Enjoy!

################################################################################################
##Script:			Get-DNS_A_Records.ps1
##
##Description:		Gets all DNS A records from a given DNS server and exports the information 
#+					to a CSV file.
##Created by:		Noam Wajnman
##Creation Date:	April 07, 2014
################################################################################################
#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$CSV = "$dir\DNS_A_records.csv"
#parameters
$DNSServer = "Some_DNS_server"
$Zone1 = "myzone.local"
$Zone2 = "me.myzone.local"
#SCRIPT MAIN
clear
$DNS_Zones = @()
$DNS_Zones += $Zone1
$DNS_Zones += $Zone2
$hosts = @()
$DNS_Zones | % {
	$zone = $_
	Write-Host "Getting DNS A records from $zone"	
	$DNS_A_records = @(Get-WmiObject -Class MicrosoftDNS_AType -NameSpace Root\MicrosoftDNS -ComputerName $DNSServer -Filter "ContainerName = `'$zone`'")
	$DNS_A_records | % {
		$hostA = "" | select "hostname","IPAddress"
		$hostA.hostname = $_.OwnerName
		$hostA.IPAddress = $_.IPAddress
		$hosts += $hostA
	}
}
$hosts = $hosts | Sort-Object @{Expression={[Version]$_.IPAddress}}
$hosts | Export-Csv $CSV -NoTypeInformation -Force

Powershell – Get last boot time on remote servers and export results to CSV

Standard

Here’s a script to help you get the last boot time from remote windows servers. If you need to check the uptime of servers or troubleshoot unexpected restarts etc. then this script can be very useful.
The script uses WMI to get the information from the remote servers and then exports the results to a CSV file in the directory where the script was run. I have structured the script in three sections variables, functions and script main. I will go over these sections one by one now.

Variables

#VARIABLES

#$DebugPreference = "continue" #uncomment to get debug info
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath #path to the directory in which the script is run.
$servers = "$dir\Servers.txt" #last boot time will be retrieved on all the servers given in this file.
$CSVPath = "$dir\LastBootTimes.csv" #results will be exported to csv to a file with this path

The object $dir always points to the directory in which the script is run and is used in the paths to files I work with in the script. This makes the script more robust because I can copy the script wherever I want without having to change any paths at all or edit the script.
$servers is the path to “servers.txt” file. You must create this file before you run the script and enter the names (one per line) of the servers you want to get the last boot time from.
$CSVPath is the path to CSV file which will be created when the script is run.

Functions

1. Get-LastBootTime

#FUNCTIONS
function Get-LastBootTime {
	[CmdletBinding()]
	[OutputType([System.String])]
	param(
		[Parameter(ValueFromPipeline=$true)][System.String[]]$server = $env:COMPUTERNAME
	)
	begin {
		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
			)
			$socket = new-object Net.Sockets.TcpClient
			$connect = $socket.BeginConnect($server, 135, $null, $null) #port set to 135 (RPC)
			$NoTimeOut = $connect.AsyncWaitHandle.WaitOne(500, $false) #timeout value set to 500 ms
			if ($NoTimeOut) {
				$socket.EndConnect($connect) | Out-Null
				return $true				
			}
			else {
				return $false
			}
		}
	}
	process {		
		$BootTime = $null
		$server = $($server).toUpper()	
		$alive = Test-PortAlive -server $server
		if ($alive) {
			Write-Debug "connection to $server is open on port $port"			
			$OSInfo = Get-WmiObject win32_operatingsystem -ComputerName $server #get the info with WMI
			$BootTime = $OSInfo.ConvertToDateTime($OSInfo.LastBootUpTime) #convert to datetime
			$BootTime = '{0:yyyy-MM-dd HH:mm:ss}' -f $BootTime #parse time to sortable format
			if ($BootTime) {
				Write-Debug "$server was rebooted last time at:`t$BootTime"					
				$result = "" | select "Server","LastBootTime" #creating a custom object
				$result.Server = $server
				$result.LastBootTime = $BootTime
				$result #return the $result object
			}
			else {
				Write-Debug "couldn't get boot time from server $server"
			}
		}			
		else {
			Write-Debug "Error - connection to $server is not open on port $port"			
		}
	}	
}

the function Get-LastBootTime takes one parameter $server which can also be passed to it via the pipeline.
Pipeline functions usually have three parts “begin”, “process” and “end” (this function only uses “begin” and “process”). In the begin section I have included another function “Test-PortAlive” but I won’t go into details about it as I have covered this in a previous post dedicated to that function. In this script I use it to test if the server is alive and the RPC port 135 is open as this is needed to run the WMI query.
The “process” part of the function is where last boot time information is retrieved using WMI. Basically the function attempts to get the info. If successful then a custom object with the properties “server” and “LastBootTime” is created and returned.

Script Main

#SCRIPT MAIN

clear
$BootTimes = @(gc $servers | Get-LastBootTime) #create and populate array with the last boot times of the given servers.
$BootTimes = $BootTimes | Sort-Object -Property "LastBootTime" #Sort array by last boot time
$BootTimes #Prints the $bootTimes array to the console 
$BootTimes | Export-Csv $CSVPath -NoTypeInformation -Force #Export the results to CSV

In the script main the first thing which happens is to create the array $BootTimes and fill it with the data from Get-LastBootTime function. This is done by running Get-Content on the servers.txt file and then piping the server names to the Get-LastBootTime function.
After this I sort the $BootTimes array by last boot time.
Finally the $BootTimes array is exported to CSV in the script directory.
I have copied in the full script below. I hope you will find it useful.
Enjoy!!

################################################################################################
##Script:			Get-LastBootTime.ps1
##
##Description:		Gets the time of the last boot on the servers given in "servers.txt" and
#+					exports the results to a csv file in the script dir.
##Created by:		Noam Wajnman
##Creation Date:	May 21, 2013
##Updated:			April 02, 2014
################################################################################################
#FUNCTIONS
function Get-LastBootTime {
	[CmdletBinding()]
	[OutputType([System.String])]
	param(
		[Parameter(ValueFromPipeline=$true)][System.String[]]$server = $env:COMPUTERNAME
	)
	begin {
		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
			)
			$socket = new-object Net.Sockets.TcpClient
			$connect = $socket.BeginConnect($server, 135, $null, $null) #port set to 135 (RPC)
			$NoTimeOut = $connect.AsyncWaitHandle.WaitOne(500, $false) #timeout value set to 500 ms
			if ($NoTimeOut) {
				$socket.EndConnect($connect) | Out-Null
				return $true				
			}
			else {
				return $false
			}
		}
	}
	process {		
		$BootTime = $null
		$server = $($server).toUpper()	
		$alive = Test-PortAlive -server $server
		if ($alive) {
			Write-Debug "connection to $server is open on port $port"			
			$OSInfo = Get-WmiObject win32_operatingsystem -ComputerName $server #get the info with WMI
			$BootTime = $OSInfo.ConvertToDateTime($OSInfo.LastBootUpTime) #convert to datetime
			$BootTime = '{0:yyyy-MM-dd HH:mm:ss}' -f $BootTime #parse time to sortable format
			if ($BootTime) {
				Write-Debug "$server was rebooted last time at:`t$BootTime"					
				$result = "" | select "Server","LastBootTime" #creating a custom object
				$result.Server = $server
				$result.LastBootTime = $BootTime
				$result #return the $result object
			}
			else {
				Write-Debug "couldn't get boot time from server $server"
			}
		}			
		else {
			Write-Debug "Error - connection to $server is not open on port $port"			
		}
	}	
}
#VARIABLES
#$DebugPreference = "continue" #uncomment to get debug info
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath #path to the directory in which the script is run.
$servers = "$dir\Servers.txt" #last boot time will be retrieved on all the servers given in this file.
$CSVPath = "$dir\LastBootTimes.csv" #results will be exported to csv to a file with this path
#SCRIPT MAIN
clear
$BootTimes = @(gc $servers | Get-LastBootTime) #create and populate array with the last boot times of the given servers.
$BootTimes = $BootTimes | Sort-Object -Property "LastBootTime" #Sort array by last boot time
$BootTimes #Prints the $bootTimes array to the console 
$BootTimes | Export-Csv $CSVPath -NoTypeInformation -Force #Export the results to CSV