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
Advertisements

Powershell – Test TCP ports on remote servers

Standard

From time to time it is necessary to check if specific TCP ports are open on remote servers. If you have many servers to check it can be a hassle to use telnet or other tools and check each server one by one. It is also often useful in other scripts to test if a remote server/port is alive before running code on them. To accomplish this I have written this little function.

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
	}
}

The function takes two parameters $server and $port. $server is the name of the remote server to test and $port is the number of the TCP port to check the status of. The $server parameter can even be passed via the pipeline making it very easy to run. The function returns either $true or $false depending on whether the port is open or not. To avoid long wait times due to closed ports I have included a relatively short timeout value of 500 ms before the result is determined.
I have included a few examples below of how to call the function.
1. Normal

 
Test-PortAlive -port 135 "some_server" 

Here I just run the function as normal and pass both params. I chose port 135 in this example but it could be any port.
2. Pipeline

 
$Array_of_Server_Names | Test-PortAlive -port 135

In this example I am using an array to pass the server names to the function via the pipeline. I again chose port 135 in this example.

That’s it. I hope you find this function useful. Enjoy!!

Powershell – Set storage LUN multipath policy on VMWare Hosts

Standard

Setting the multipath policy on ESXi hosts can be long process as it must be configured on every LUN on every host. If one has a lot of storage LUNs used by hosts/VMs this can quickly become a huge amount of tedious and repetitive work. However this script solves that problem and will let you change the configuration on all ESXi hosts and LUNs with ease.
This script will configure all fibre channel LUNs on a given ESXi host to use the value “fixed” or “round robin” depending on what you specify. The script can also be set to do this on all hosts in your HA cluster if needed.
The script is fairly small but I have divided it into three sections variables, functions and script main to simplify the structure.

Variables

#VARIABLES
#parameters
$vCenter = "your_vcenter_server"
$ESXi = "ESXi_FQDN" #use if configuring a single host
$clusterName = "Some_Cluster" #use if configuring all the hosts in a cluster
$MultiPathPolicy = "RoundRobin" #usable values are "Fixed" and "RoundRobin"

You need to fill out the parameters section before running the script
$Vcenter should be the name of your vcenter server
$ESXi should be the name of the ESXi host that you want to configure.
$ClusterName should be the name of the cluster you want configure. All LUNs on the hosts on this cluster will be set to the chosen multipath policy.
$MultiPathPolicy should be set to the value you want to configure on the host/cluster.

Functions

1. Function Set-ESXiMultiPathPolicy

function Set-ESXiMultiPathPolicy {
	param (
		$ESXi #FQDN of ESXi host
	)
	get-VMHost "$ESXi" | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType "disk" `
	| where {$_.MultipathPolicy -ne $MultiPathPolicy} | Set-ScsiLun -MultipathPolicy $MultiPathPolicy
}

The script only has this one function which takes one parameter $ESXi which is the ESXi host to configure the path policy on. It loops through all the LUNs on the given host and sets the multipath policy to the chosen value if not already configured like this.

Script Main

#SCRIPT MAIN
clear
#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
}
#use this line to only configure one host ($ESXi)
Set-ESXiMultiPathPolicy -ESXi $ESXi

#Use this line to configure all the hosts in the cluster $ClusterName
#Get-Cluster $clusterName | Get-VMHost | % { Set-ESXiMultiPathPolicy -ESXi $_.Name}

The script main first checks if the PS Snap-in VMWare.VimAutomation.Core is loaded. If not the snap-in and commands are loaded. The script then connects to vcenter if it is not already connected. the last couple of lines in the script are the actual commands which run the functions and do the work. Only one of the lines should be run (the other must be commented out) depending on whether you want to configure a single host or all the hosts on a cluster.
That’s it. Enjoy!!
I have copied the full script in below.

################################################################################################
##Script:			Set-ESXiMultiPathPolicy.ps1
##
##Description:		Script configures the multipath policy for all LUNs on a given host or 
#+					cluster.
##Created by:		Noam Wajnman
##Creation Date:	February 2, 2013
##Updated:			March 25, 2014
################################################################################################
#FUNCTIONS
function Set-ESXiMultiPathPolicy {
	param (
		$ESXi #FQDN of ESXi host
	)
	get-VMHost "$ESXi" | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType "disk" `
	| where {$_.MultipathPolicy -ne $MultiPathPolicy} | Set-ScsiLun -MultipathPolicy $MultiPathPolicy
}
#VARIABLES
#parameters
$vCenter = "your_vcenter_server"
$ESXi = "ESXi_FQDN" #use if configuring a single host
$clusterName = "Some_Cluster" #use if configuring all the hosts in a cluster
$MultiPathPolicy = "RoundRobin" #usable values are "Fixed" and "RoundRobin"
#SCRIPT MAIN
clear
#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
}
#use this line to only configure one host ($ESXi)
Set-ESXiMultiPathPolicy -ESXi $ESXi

#Use this line to configure all the hosts in the cluster $ClusterName
#Get-Cluster $clusterName | Get-VMHost | % { Set-ESXiMultiPathPolicy -ESXi $_.Name}

Powershell – Import certificates on remote servers

Standard

A while back I was tasked with importing some certificates on all our web servers. I didn’t like the idea of doing this manually on hundreds of servers so I decided I had to write a script which could help me with this job. The problem was that I couldn’t find any “easy” way of importing certificates on remote servers. It is possible with some commercial tools and also with windows 2012 R2 and powershell v4 but none of these options were available to me. Furthermore I wanted to have a script which was generic and could be used on all windows servers regardless of version. This led me to create this powershell script which I will go over for you now.

The script loops through the servers in the “servers.txt” file (you must create this file and put it in the script directory) and basically works as outlined in these steps:
1. The certificate is copied to the remote server
2. A custom mini-scipt for importing the certificate is created and copied to the remote server.
3. The mini-script is invoked by using psexec and imported into the desired certificate store.
4. The certificate and mini-script are deleted on the remote server after use.
5. The result of the certificate import is checked.

A log file with relevant information is created when the script is run, making it possible to confirm the successful import later on.
The script is divided into three sections – variables, functions and script main.

Variables

#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$RemoteCertDir = "c:\temp\certs\" #temporary file location on remote server
$logfile = "$dir\logs\" + "cert-bulkimport_" + $(get-date -Format "yyyy-MM-dd_HH-mm-ss") + ".log"
$servers = gc "$dir\servers.txt"
$psexec = "$dir\psexec.exe"
#parameters
$DebugPreference = "continue" #comment out to disable debug info
$certRootStore = "localmachine"
$certStore = "My"
$Cert = "$dir\certs\some_cert.pfx" #certificate to import
$CertPW = "Password" #password for certificate to import
$CertCN = "some.domain.com" #common name of certificate to import

The first two lines of code –
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath

are really practical as it gives me an object $dir which always points to where the script was run. This makes the script more robust as I can freely move the script around and run it from many locations without having to change any paths in the script.
$logfile is the path of the log file which will be created for each script run. The file name will contain a time stamp to avoid overwriting the file.
$servers is an array populated by running Get-Content on the “servers.txt” file.
$psexec is the path to psexec.exe (place it in the same directory as the script).
The parameters section is really important and you need to fill these variables with the specific information about your certificate and which store to import them to. The descriptions in the code comments should explain how to do this.

Functions

1. Function Log-Entry

function log-entry {
    param(
        $message
    )
    $time = Get-date
    Add-Content -Path $logfile -Value "$time`t$message"
}

This function takes a parameter “message” and appends it to the log file with a timestamp. I use it to easily write to the log file throughout the script.

2. Function RemoteCopy-Certificate

function RemoteCopy-Certificate {
    param(
        $server
    )
    $copyresult = $false
    $RemoteDir = "\\$server\" + $RemoteCertDir.replace(":","$")	
    Write-Debug "Creating directory $RemoteDir"
    log-entry -message "Creating directory $RemoteDir"	
    New-Item -ItemType "directory" $RemoteDir -Force
    $RemoteCertPath = $RemoteDir + (gci $Cert).Name	
    Write-Debug "Copying $Cert to $RemoteDir"
    log-entry -message "Copying $Cert to $RemoteDir"	
    Copy-Item -Path $Cert -Destination $RemoteCertPath -Force
    if (Test-Path -Path $RemoteCertPath) {
        $CopyResult = $true		
        Write-Debug "Copy of $Cert to $RemoteDir succeeded."
        log-entry -message "Copy of $Cert to $RemoteDir succeeded." 
    }
    else {
        $CopyResult = $false 		
        Write-Debug "Error - Copy of $Cert to $RemoteDir failed."
        log-entry -message "Error - Copy of $Cert to $RemoteDir failed."
    }
    return $CopyResult
}

This function creates a temp directory on the remote server and copies the certificate to there. It then checks if the certificate is there and returns $true if the copy was successful.

3. Function Create-ImportScript

function Create-ImportScript {
    param(
        $server
    )
    $ScriptCreateResult = $false
    $RemoteScriptPath = "\\$server\" + $RemoteCertDir.Replace(":","$") + "import-pfx.ps1"    
    $CertPath = "'" + $RemoteCertDir + (gci $Cert).Name + "'"    
    $crtPass = "'" + $CertPW + "'"
    $SB = @"
`n
`$crt = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
`$crt.import($certPath,$crtPass,"Exportable,PersistKeySet")
`$store = new-object System.Security.Cryptography.X509Certificates.X509Store(`'$certStore`',`'$certRootStore`') 
`$store.open("MaxAllowed") 
`$store.add(`$crt) 
`$store.close()
"@
    log-entry -message "Creating import script $RemoteScriptPath"
    Write-Debug "Creating import script $RemoteScriptPath`:$SB`n"	
    $SB | Out-File $RemoteScriptPath -force
    if (Test-Path -Path $RemoteScriptPath) {
        $ScriptCreateResult = $true		
        Write-Debug "Creation of $RemoteScriptPath succeeded."
        log-entry -message "Creation of $RemoteScriptPath succeeded." 
    }
    else {
        $ScriptCreateResult = $false		
        Write-Debug "Error - Creation of $RemoteScriptPath failed."
        log-entry -message "Error - Creation of $RemoteScriptPath failed."
    }
    return $ScriptCreateResult    
}

Here, a custom powershell mini import script is created. Basically the mini script comes from the multi-line string $SB which is redirected to create a .ps1 file. The function then checks if the file was created on the remote server and returns $true for a successful result.

4. Function Invoke-Importscript

function Invoke-Importscript {
    param(
        $server
    )
    $ImportScriptPath = $RemoteCertDir + "import-pfx.ps1"
    $arg1 = "/acceptEula"
    $arg2 = "\\$server"
    $arg3 = "cmd /c"
    $arg4 = "`"echo .|powershell -file $ImportScriptPath`""	
    Write-Debug "invoking remote import script on $server"   
    log-entry -message "invoking remote import script on $server"	
    Start-Process -Wait -FilePath $psexec -ArgumentList "$arg1 $arg2 $arg3 $arg4"
}

This function runs the mini script on the remote server and imports the certificate into the chosen store. Psexec.exe is used to invoke the script on the remote server.

5. Function Remove-TempFiles

function Remove-TempFiles {
    param(
        $server
    )
    $RemoteScriptDir = "\\$server\" + $RemoteCertDir.Replace(":","$")	
    Write-Debug "Removing temporary cert and script files from $RemoteScriptDir"
    log-entry -message "Removing temporary cert and script files from $RemoteScriptDir"	
    gci -Recurse -Path $RemoteScriptDir | Remove-Item
}

As we don’t want to leave any certs or scripts behind after we are done I wrote this function to remove these files after the cert has been imported.

6. Function List-RemoteCerts

function List-RemoteCerts {
    param(
        $server
    )
    $ro = [System.Security.Cryptography.X509Certificates.OpenFlags]"ReadOnly"
    $lm = [System.Security.Cryptography.X509Certificates.StoreLocation]"LocalMachine"
    $store = new-object System.Security.Cryptography.X509Certificates.X509Store("\\$server\$CertStore", $lm)
    $store.Open($ro)
    return $store.Certificates
}

This function lists the certificates on the remote server. I use this function in the next function Confirm-CertImport.

7. Function Confirm-CertImport

function Confirm-CertImport {
    param(
        $server
    )
    Write-Debug "Checking if certificate import on $server was successful."
    log-entry -message "Checking if certificate import on $server was successful."	
    $IsCertThere = $false    
    $MyCerts = List-RemoteCerts -server $server        
    $MyCerts | % {	
        Write-Debug "Found certificate: $($_.Subject)"		
        if ($_.Subject -like "*$CertCN*") {		
            Write-Debug "*************Certificate match for $CertCN found!)"			
            $IsCertThere = $true
        }
    }
    return $IsCertThere       
}

Here I loop through the certificates found on the remote server. If the certificate we just attempted to import is found a result of $true is returned.

Script Main

#SCRIPT MAIN
new-item -itemtype "file" $logfile -force | out-null #create the log file for this script execution
Write-Debug "Script run started."
log-entry -message "Script run started."
$servers | % { #loop through the servers which we will import the certificate to
    Write-Debug "Starting cert import on $_".ToUpper()
    log-entry "Starting cert import on $_".ToUpper()
    $copyResult = RemoteCopy-Certificate -server $_ # Find out if the copy of the cert to the server was successful
    if ($copyResult) {   #If the copy of the certificate to the server succeeded then proceed.     
        $ScriptCreateResult = Create-ImportScript -server $_ #Find out if the script was created and copied to the server successfully
        if ($ScriptCreateResult) { #if the script was created/copied to the server successfully then proceed.
            Invoke-Importscript -server $_ #import the certificate into the specified store
            Remove-TempFiles -server $_ #Remove temp files such as the cert and import script
            $CertImportResult = Confirm-CertImport -server $_ #check that the cert was imported successfully and write the result to the log.
            if ($CertImportResult) {
                Write-Debug "Success! $CertCN was successfully imported on $_"
                log-entry -message "Success! $CertCN was successfully imported on $_"
            }
            else {
                Write-Debug "Error! Import of $CertCN on $_ failed."
                log-entry -message "Error! Import of $CertCN on $_ failed."
            }
        }
    }
    Write-Debug "-------------------------------------------------------------------------------------------------------------------------"
    log-entry "`n`n"
}

The script main starts with creating the log file used for the script run. I then loop through the $servers array and with some nested IF sentences run the functions described above.
When the script run has completed, the certificate will be imported on the servers specified in the “servers.txt” file and you will have a log file with the import results available.
I hope you find the script useful. Enjoy!!
I have copied in the full script below.

##################################################################################################################
##RemoteImport-Certificate.ps1
##Description: 		Script imports a certificate into all remote servers specified in the 
#+                  "servers.txt" inputlist.
#+					Fill out the "parameters" section according to your needs before running!
##Created by:		Noam Wajnman
##Creation Date:	March 17, 2014
###################################################################################################################
#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$RemoteCertDir = "c:\temp\certs\" #temporary file location on remote server
$logfile = "$dir\logs\" + "cert-bulkimport_" + $(get-date -Format "yyyy-MM-dd_HH-mm-ss") + ".log"
$servers = gc "$dir\servers.txt"
$psexec = "$dir\psexec.exe"
#parameters
$DebugPreference = "continue" #comment out to disable debug info
$certRootStore = "localmachine"
$certStore = "My"
$Cert = "$dir\certs\some_cert.pfx" #certificate to import
$CertPW = "Password" #password for certificate to import
$CertCN = "some.domain.com" #common name of certificate to import
#FUNCTIONS
function log-entry {
    param(
        $message
    )
    $time = Get-date
    Add-Content -Path $logfile -Value "$time`t$message"
}
function RemoteCopy-Certificate {
    param(
        $server
    )
    $copyresult = $false
    $RemoteDir = "\\$server\" + $RemoteCertDir.replace(":","$")	
    Write-Debug "Creating directory $RemoteDir"
    log-entry -message "Creating directory $RemoteDir"	
    New-Item -ItemType "directory" $RemoteDir -Force
    $RemoteCertPath = $RemoteDir + (gci $Cert).Name	
    Write-Debug "Copying $Cert to $RemoteDir"
    log-entry -message "Copying $Cert to $RemoteDir"	
    Copy-Item -Path $Cert -Destination $RemoteCertPath -Force
    if (Test-Path -Path $RemoteCertPath) {
        $CopyResult = $true		
        Write-Debug "Copy of $Cert to $RemoteDir succeeded."
        log-entry -message "Copy of $Cert to $RemoteDir succeeded." 
    }
    else {
        $CopyResult = $false 		
        Write-Debug "Error - Copy of $Cert to $RemoteDir failed."
        log-entry -message "Error - Copy of $Cert to $RemoteDir failed."
    }
    return $CopyResult
}
function Create-ImportScript {
    param(
        $server
    )
    $ScriptCreateResult = $false
    $RemoteScriptPath = "\\$server\" + $RemoteCertDir.Replace(":","$") + "import-pfx.ps1"    
    $CertPath = "'" + $RemoteCertDir + (gci $Cert).Name + "'"    
    $crtPass = "'" + $CertPW + "'"
    $SB = @"
`n
`$crt = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
`$crt.import($certPath,$crtPass,"Exportable,PersistKeySet")
`$store = new-object System.Security.Cryptography.X509Certificates.X509Store(`'$certStore`',`'$certRootStore`') 
`$store.open("MaxAllowed") 
`$store.add(`$crt) 
`$store.close()
"@
    log-entry -message "Creating import script $RemoteScriptPath"
    Write-Debug "Creating import script $RemoteScriptPath`:$SB`n"	
    $SB | Out-File $RemoteScriptPath -force
    if (Test-Path -Path $RemoteScriptPath) {
        $ScriptCreateResult = $true		
        Write-Debug "Creation of $RemoteScriptPath succeeded."
        log-entry -message "Creation of $RemoteScriptPath succeeded." 
    }
    else {
        $ScriptCreateResult = $false		
        Write-Debug "Error - Creation of $RemoteScriptPath failed."
        log-entry -message "Error - Creation of $RemoteScriptPath failed."
    }
    return $ScriptCreateResult    
}
function Invoke-Importscript {
    param(
        $server
    )
    $ImportScriptPath = $RemoteCertDir + "import-pfx.ps1"
    $arg1 = "/acceptEula"
    $arg2 = "\\$server"
    $arg3 = "cmd /c"
    $arg4 = "`"echo .|powershell -file $ImportScriptPath`""	
    Write-Debug "invoking remote import script on $server"   
    log-entry -message "invoking remote import script on $server"	
    Start-Process -Wait -FilePath $psexec -ArgumentList "$arg1 $arg2 $arg3 $arg4"
}
function Remove-TempFiles {
    param(
        $server
    )
    $RemoteScriptDir = "\\$server\" + $RemoteCertDir.Replace(":","$")	
    Write-Debug "Removing temporary cert and script files from $RemoteScriptDir"
    log-entry -message "Removing temporary cert and script files from $RemoteScriptDir"	
    gci -Recurse -Path $RemoteScriptDir | Remove-Item
}
function List-RemoteCerts {
    param(
        $server
    )
    $ro = [System.Security.Cryptography.X509Certificates.OpenFlags]"ReadOnly"
    $lm = [System.Security.Cryptography.X509Certificates.StoreLocation]"LocalMachine"
    $store = new-object System.Security.Cryptography.X509Certificates.X509Store("\\$server\$CertStore", $lm)
    $store.Open($ro)
    return $store.Certificates
}
function Confirm-CertImport {
    param(
        $server
    )
    Write-Debug "Checking if certificate import on $server was successful."
    log-entry -message "Checking if certificate import on $server was successful."	
    $IsCertThere = $false    
    $MyCerts = List-RemoteCerts -server $server        
    $MyCerts | % {	
        Write-Debug "Found certificate: $($_.Subject)"		
        if ($_.Subject -like "*$CertCN*") {		
            Write-Debug "*************Certificate match for $CertCN found!)"			
            $IsCertThere = $true
        }
    }
    return $IsCertThere       
}
#SCRIPT MAIN
new-item -itemtype "file" $logfile -force | out-null #create the log file for this script execution
Write-Debug "Script run started."
log-entry -message "Script run started."
$servers | % { #loop through the servers which we will import the certificate to
    Write-Debug "Starting cert import on $_".ToUpper()
    log-entry "Starting cert import on $_".ToUpper()
    $copyResult = RemoteCopy-Certificate -server $_ # Find out if the copy of the cert to the server was successful
    if ($copyResult) {   #If the copy of the certificate to the server succeeded then proceed.     
        $ScriptCreateResult = Create-ImportScript -server $_ #Find out if the script was created and copied to the server successfully
        if ($ScriptCreateResult) { #if the script was created/copied to the server successfully then proceed.
            Invoke-Importscript -server $_ #import the certificate into the specified store
            Remove-TempFiles -server $_ #Remove temp files such as the cert and import script
            $CertImportResult = Confirm-CertImport -server $_ #check that the cert was imported successfully and write the result to the log.
            if ($CertImportResult) {
                Write-Debug "Success! $CertCN was successfully imported on $_"
                log-entry -message "Success! $CertCN was successfully imported on $_"
            }
            else {
                Write-Debug "Error! Import of $CertCN on $_ failed."
                log-entry -message "Error! Import of $CertCN on $_ failed."
            }
        }
    }
    Write-Debug "-------------------------------------------------------------------------------------------------------------------------"
    log-entry "`n`n"
}

Powershell – Get OS install date from remote servers

Standard

Here’s a handy little script to get the OS install date from your servers and export all the information to a CSV file.
The script is split into three sections variables, functions and script main. The script loops through all servers in the “servers.txt” file (must be placed in the same directory as the script) and gets the OS install date using WMI.

Variables

Below is a short walkthrough of the variables section of the script.

#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$servers = @(gc "$dir\servers.txt")

The first two lines of code –
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath

are really practical as it gives me an object $dir which always points to where the script was run. This makes the script more robust as I can freely move the script around and run it from many locations without having to change any paths in the script.
$servers is an array populated by running Get-Content on the servers.txt file.

Functions

The script uses two functions to do most of the work.
1. Function Get-OSInstallDate

function Get-OSInstallDate {
	param (
		$computerName = $Env:COMPUTERNAME
	)
	[datetime]$OSInstallDate = ([WMI]'').ConvertToDateTime((Get-WmiObject Win32_OperatingSystem -ComputerName $computerName).InstallDate)
	if ($?) {
		return $OSInstallDate
	}
}

This function takes a parameter $computerName and uses the WMI class Win32_OperatingSystem to get the OS install date. The OS install date is also converted so we can return it as a neat datetime object.
2. Function ExportSpecial-CSV

function ExportSpecial-CSV {
	param (
		$inputArray,
		$InfoType		
		)	
    $inputArray | Export-Csv "$dir\$InfoType.csv" -noTypeInformation -Force  
}

This function is extremely simple. It takes an array as input and exports it to CSV in the script directory.

Script Main

$OSInstallDates = @()
$servers | % {
	$date = Get-OSInstallDate -computerName $_
	if ($date) {
		$OSInstallDate = "" | Select "Computer","OSInstallDate"
		$OSInstallDate.Computer = $_
		$OSInstallDate.OSInstallDate = $date
		$OSInstallDates += $OSInstallDate
	}
}
ExportSpecial-CSV -inputArray $OSInstallDates -InfoType "OS_Install_dates"

In the script main I loop through $servers and run the Get-OSInstallDate function to get the info. If the OS install date was retrieved successfully A custom object $OSInstallDate with two properties “Computer” and “OSInstallDate” is created and then added to the array $OSInstallDates. After the foreach loop I use the function ExportSpecial-CSV to export the array $OSInstallDates to the script directory.
That’s it!
Below is the full script. Good luck!

##################################################################################################################
##Script:			Get-OSInstallDate.ps1
##Description: 		Script uses WMI to get the OS install date from the servers listed in "servers.txt" and then 
#+					exports the information to CSV.
##Usage:			Create a file "servers.txt" in the script directory and put in the names of the servers to query.
#+					Run the script and it will create a file called "OS_Install_Dates.csv" in the same directory.
##Created by:		Noam Wajnman
##Creation Date:	February 5, 2014
###################################################################################################################
#FUNCTIONS
function Get-OSInstallDate {
	param (
		$computerName = $Env:COMPUTERNAME
	)
	[datetime]$OSInstallDate = ([WMI]'').ConvertToDateTime((Get-WmiObject Win32_OperatingSystem -ComputerName $computerName).InstallDate)
	if ($?) {
		return $OSInstallDate
	}
}
function ExportSpecial-CSV {
	param (
		$inputArray,
		$InfoType		
		)	
    $inputArray | Export-Csv "$dir\$InfoType.csv" -noTypeINformation -Force  
}
#VARIABLES
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$servers = @(gc "$dir\servers.txt")
#SCRIPT MAIN
$OSInstallDates = @()
$servers | % {
	$date = Get-OSInstallDate -computerName $_
	if ($date) {
		$OSInstallDate = "" | Select "Computer","OSInstallDate"
		$OSInstallDate.Computer = $_
		$OSInstallDate.OSInstallDate = $date
		$OSInstallDates += $OSInstallDate
	}
}
ExportSpecial-CSV -inputArray $OSInstallDates -InfoType "OS_Install_dates"