#v1.4 - Updating to write verbose #v1.3 - New media server stuff #v1.2 - Fixing logging so it seperates Qcs and only logs once #v1.1 - Issue when servername was not the same as core, also, psexec really hates it when you specify the computer name as domain, so changed to "." #v1.0 - full release #v0.0 - Initial build #$DebugPreference = "Inquire" # Written by: Martin Howlett martin.howlett@rackspace.co.uk write-verbose "execution module version 1.4" Function Set-WHAMFolderPaths{ $Username = [Environment]::UserName #Make sure the folders are present $Global:WHAMLogPath = "" $Global:WHAMExportPath = "" $DesktopPath = [Environment]::GetFolderPath("Desktop") $Global:WHAMExportPath = "$DesktopPath\WHAM" IF (-NOT (Test-Path $Global:WHAMExportPath)) { New-Item -ItemType directory -Path $WHAMExportPath -ErrorAction SilentlyContinue | Out-Null } } Function Test-WinRMDate { <# .Synopsis Tests CORE online date for WinRM compatibility .DESCRIPTION confirms the online date is after October 2014 - when WINRM was enabled in the kick .EXAMPLE Test-WinRMDate -dateonline "013-05-30 03:01:42.00" .EXAMPLE Test-WinRMDate -dateonline $Computer.dateonline .OUTPUTS $true if WinRM enabled, $false if not (according to date) #> Param( [Parameter()]$dateonline ) #Make sure the device is going to have WINRM enabled (from 10/2014) [String]$Online = $dateonline If ($online.Length -eq 0) { #Date from CORE is blank, so just return false $WinRMOnline = $false } ELSE { $OnlineYear = ($Online.Split(" ")[0]).Split("-")[0] $OnlineMonth = ($Online.Split(" ")[0]).Split("-")[1] #check date it was onlined IF (($OnlineYear -lt 2014) -and ($OnlineMonth -lt 10) -and ($dateonline.Length -ne 0)) { $WinRMOnline = $false } ELSE { $WinRMOnline = $true } } Return $WinRMOnline } Function Test-CoreStatus { <# .Synopsis Test CORE status is online .DESCRIPTION confirms the CORE status is one that should be online and returns $true or $false .EXAMPLE Test-CoreStatus -Status "Online/Compute" .OUTPUTS $true if CORE status is good, $false if not #> Param( [Parameter(Mandatory=$true)]$Status ) $Statuses = @( "Online/Complete" "Segment Configuration" "Support Maintenance" "Ready for Account Team" "Received Contract" "Migration" ) If ($Statuses -contains $Status) { $OnlineStatus = $true } ELSE { $OnlineStatus = $false } Return $OnlineStatus } ##NOT WORKING VVVVVVVV <# Function Test-Ping{ .Synopsis Simple ping via .Net, faster then Test-Connection .DESCRIPTION Sends a simple packet instead of using test-connection, making it much faster .EXAMPLE Test-Ping -IP 127.0.0.1 .OUTPUTS $true if ping responds, $false if not Param( [Parameter(Mandatory=$true)]$ComputerName ) #ping first for reachability check $PingOptions = New-Object net.NetworkInformation.PingOptions $PingOptions.set_ttl(64) $PingOptions.set_dontfragment($true) [Byte[]] $pingbytes = (65,72,79,89) $ping = new-object Net.NetworkInformation.Ping $savedEA = $Erroractionpreference $ErrorActionPreference = "silentlycontinue" $pingres = $ping.send($server, 1000, $pingbytes, $PingOptions) if (-not $?) {return $false} $ErrorActionPreference = $savedEA if ($pingres.status -eq "Success") { return $true }ELSE { return $false } } #> ##NOT WORKING ^^^^^^^^ Function Test-Ping{ Param( [Parameter(Mandatory=$true)]$ServerName ) Return (Test-Connection -Quiet -Count 1 -ComputerName $ServerName) } Function Test-PingGroup { <# .Synopsis Ping a server via DNS and IP .DESCRIPTION Simple ping via .Net, faster then Test-Connection .EXAMPLE Test-Ping -IP 127.0.0.1 -Servername 555555-Server -Domain lon.intensive.int .OUTPUTS $PSObject with DNS and IP set to true/false #> Param( [Parameter(Mandatory=$true)]$IP, [Parameter(Mandatory=$true)]$ServerName, [Parameter(Mandatory=$true)]$Domain ) If ($domain -like "workgroup*") { $DNS = $false } ELSE { #Test the DNS name of the server $DNS = Test-Ping -ServerName "$ServerName.$Domain" } #Test the IP of the server $IP = Test-Ping -ServerName "$IP" $Output = New-Object PSObject -Property @{ DNS = $DNS IP = $IP } Return $Output } Function Test-PSPath { <# .Synopsis Test a path on a remote machine .DESCRIPTION Maps a new PSDrive with specified credentials to ensure connection is successful .EXAMPLE Test-PSPath -Username Rack -Password 5saefwefw -Domain lon.intensive.int -Path \\92.1.56.4\c$ .OUTPUTS true if connection successful, false if not #> Param( [Parameter(Mandatory=$true)]$Username, [Parameter(Mandatory=$true)]$Password, [Parameter(Mandatory=$true)]$Domain, [Parameter(Mandatory=$true)]$Path ) $Username = "$Domain\$Username" $Password = $Password | ConvertTo-SecureString -asPlainText -Force $Credentials = New-Object System.Management.Automation.PSCredential($Username,$Password) #remove our drives before testing again Remove-PSDrive Test -ErrorAction SilentlyContinue | Out-Null $x = & net use $Path /d 2> $null #create a new ps drive using the credentials $x = New-PSDrive -PSProvider FileSystem -Name Test -Root $Path -Credential $Credentials -ErrorAction SilentlyContinue | Out-Null #test path to the admin share Try { $Result = Test-Path -Path $Path -ErrorAction Stop } Catch { $Result = $false } #remove the drive from powershell and net use Remove-PSDrive Test -ErrorAction SilentlyContinue | Out-Null $x = & net use $Path /d 2> $null Return $Result } Function Test-Credentials { Param( [Parameter(Mandatory=$true)]$IP, [Parameter(Mandatory=$true)]$ServerName, [Parameter(Mandatory=$true)]$RackPass, [Parameter()]$IntensiveUser, [Parameter()]$IntensivePass, [Parameter(Mandatory=$true)]$AdminPass, [Parameter()]$DomainUser, [Parameter()]$DomainPass, [Parameter(Mandatory=$true)]$Domain ) <# .Synopsis Test all credentials from CORE on a server .DESCRIPTION Maps a new PSDrive with all credentials from CORE until it finds a working set .EXAMPLE Test-Credentials -IP 92.1.56.4 -ServerName 555555-test -RackPass 5saefwefw -IntensiveUser Martin.howlett -IntensivePass ergreger -AdminPass wfwefef -domain lon.intensive.int .OUTPUTS A PSObject with: $ValidCreds = Customer/Intensive/Rack/Admin $Domain = working domain $Username = working user account $password = working password #> #Set out path for the test $Path = "\\$($IP)\c$" #Test customer domain creds If ($DomainUser.length -ne 0){ #Call our Test-PSPath function to get the result $CustomerResult = Test-PSPath -Domain $Domain -Username $DomainUser -Password $DomainPass -Path $Path #If the result is successful, set the working creds variable if it is already blank IF (($CustomerResult) -AND ($ValidCreds.Length -eq 0)) { $ValidCreds = "Customer" $ValidDomain = $Domain $ValidUsername = $DomainUser $ValidPassword = $DomainPass }ELSE{ $CustomerResult = "Failed" } }ELSE{ $CustomerResult = "Not tested" } #Test intensive If (-NOT $ValidCreds){ If ($IntensiveUser.length -ne 0){ #Call our Test-PSPath function to get the result $IntensiveResult = Test-PSPath -Domain "Intensive" -Username $IntensiveUser -Password $IntensivePass -Path $Path IF (($IntensiveResult) -AND ($ValidCreds.Length -eq 0)){ $ValidCreds = "Intensive" $ValidDomain = "Intensive" $ValidUsername = $IntensiveUser $ValidPassword = $IntensivePass }ELSE{ $IntensiveResult = "Failed" } }ELSE{ $IntensiveResult = "Not tested" } } #Test Rack account If (-NOT $ValidCreds){ #Call our Test-PSPath function to get the result $RackResult = Test-PSPath -Domain $ServerName -Username "Rack" -Password $RackPass -Path $Path #If the result is successful, set the working creds variable if it is already blank IF (($RackResult) -AND ($ValidCreds.Length -eq 0)){ $ValidCreds = "Rack" $ValidDomain = "." $ValidUsername = "Rack" $ValidPassword = $RackPass }ELSE{ $RackResult = "Failed" } }ELSE{ $RackResult = "Not tested" } #Test local admin If (-NOT $ValidCreds){ #Call our Test-PSPath function to get the result $DeviceID = $Servername.Split("-")[0] $AdminUsername = "$DeviceID-Admin" $AdminResult = Test-PSPath -Domain $ServerName -Username $AdminUsername -Password $AdminPass -Path $Path #If the result is successful, set the working creds variable if it is already blank IF (($AdminResult) -AND ($ValidCreds.Length -eq 0)){ $ValidCreds = "Admin" $ValidDomain = "." $ValidUsername = $AdminUsername $ValidPassword = $AdminPass }ELSE{ $AdminResult = "Failed" } }ELSE{ $AdminResult = "Not tested" } IF ($ValidCreds.Length -eq 0){ $ValidCreds = "None" } #Return our working credentials $Output = New-Object PSObject -Property @{ ValidCredentials = $ValidCreds ValidDomain = $ValidDomain ValidUsername = $ValidUsername ValidPassword = $ValidPassword } Return $Output } Function Connect-Winrm { Param( [Parameter(Mandatory=$true)]$Username, [Parameter(Mandatory=$true)]$Password, [Parameter(Mandatory=$true)]$Userdomain, [Parameter(Mandatory=$true)]$ComputerDomain, [Parameter(Mandatory=$true)]$ServerName, [Parameter(Mandatory=$true)]$Scriptblock ) Try { If ($Userdomain -eq ".") { $Userdomain = $ServerName } #Turning logging off #New-Item -Path $ErrorLogPath -Force -ItemType file -ErrorAction SilentlyContinue| Out-Null $PasswordSec = $Password | ConvertTo-SecureString -asPlainText -Force $ConnectUsername = "$UserDomain\$Username" $Computer = "$ServerName.$ComputerDomain" $credentials = New-Object System.Management.Automation.PSCredential($ConnectUsername,$PasswordSec) $WinRMParams = @{ ComputerName = $Computer; Credential = $credentials; UseSSL = $true; ScriptBlock = $scriptblock; Port = "5986"; #ErrorAction = "Stop"; SessionOption = (New-PSSessionOption -SkipCACheck -SkipCNCheck); Authentication = "Negotiate" } #Turning logging off #Add-Content -Path $ErrorLogPath -Value "WinRMParams = $($WinRMParams | Out-String)" $Winrmoutput = Invoke-Command @WinRMParams 2>&1 IF ($Winrmoutput -like "*failed with the following error message*") { $WinRMError = $true If ($Winrmoutput -like "*client cannot connect to the destination specified in the request*") { $WinRMErrorReason = "WinRM not enabled on remote server" } If ($Winrmoutput -like "*Access is denied*") { $WinRMErrorReason = "Credentials are incorrect" } } $Output = New-Object PSObject $Output | Add-Member -type NoteProperty -Name "Computer" -Value $Computer $Output | Add-Member -type NoteProperty -Name "WinRMError" -Value $WinRMError $Output | Add-Member -type NoteProperty -Name "WinRMErrorReason" -Value $WinRMErrorReason $Output | Add-Member -type NoteProperty -Name "Winrmoutput" -Value $Winrmoutput Return $Output } Catch { $WinRMError = $true $WinRMErrorReason = "Credentials are incorrect" $Output = New-Object PSObject $Output | Add-Member -type NoteProperty -Name "Computer" -Value $Computer $Output | Add-Member -type NoteProperty -Name "WinRMError" -Value $WinRMError $Output | Add-Member -type NoteProperty -Name "WinRMErrorReason" -Value $WinRMErrorReason $Output | Add-Member -type NoteProperty -Name "Winrmoutput" -Value $Winrmoutput Return $Output } Return $Output } Function Test-WinRMConnection { <# .Synopsis Tests WinRM is working on a server .DESCRIPTION Runs Connect-Winrm on server and confirms it is returning data correctly .EXAMPLE Test-WinRM -Username Rack -Password ddsdvwsvdre -Userdomain $Servername -Computerdomain lon.intensive.int -Userdomain $Servername .OUTPUTS $true if winrm works, $false if it doesnt #> Param ( [Parameter(Mandatory=$true)]$Username, [Parameter(Mandatory=$true)]$Password, [Parameter(Mandatory=$true)]$Userdomain, [Parameter(Mandatory=$true)]$ComputerDomain, [Parameter(Mandatory=$true)]$ServerName ) Try { $WinRMParams = @{ username = $Username; Password = $Password; Userdomain = $Userdomain; ComputerDomain = $ComputerDomain; ServerName = $ServerName; Scriptblock = {$ENV:ComputerName} } $WinRMOUtput = Connect-Winrm @WinRMParams IF ($WinRMOUtput.Winrmoutput -eq $ServerName) { $WinRMResult = $True } ELSE { $WinRMResult = $False $WinrmFailureReason = $WinRMOUtput.WinRMErrorReason } } Catch { $WinRMResult = $False $WinrmFailureReason = $WinRMOUtput.WinRMErrorReason } $Output = New-Object PSObject -Property @{ WinrmFailureReason = $WinrmFailureReason WinrmSuccess = $WinRMResult } return $output } Function Test-WHAMConnectivity { <# .Synopsis Gets data on ping, ports and credentials on a server .DESCRIPTION Runs tests to verify ping, ports and credentials are accessible a server .EXAMPLE Test-WinRM -Username Rack -Password ddsdvwsvdre -Userdomain $Servername -Computerdomain lon.intensive.int -Userdomain $Servername .OUTPUTS PSObject with following: OnlineStatus - true/false depending if connectable status from CORE WinRMDate - true/false depending if WinRM date PingDNS - true/false on ping result via DNS PingIP - true/false on ping result via IP Port139 - true/false on port check result Port445 - true/false on port check result Port5986 - true/false on port check result ValidCredentials - type of valid credentials found - rack/intensive/customer/admin ValidDomain - valid domain for user credentials ValidUsername - valid username ValidPassword - valid password OnlineStatus #> Param ( [Parameter()]$dateonline, [Parameter(Mandatory=$true)]$status, [Parameter(Mandatory=$true)]$IP, [Parameter(Mandatory=$true)]$ServerName, [Parameter(Mandatory=$true)]$Domain, [Parameter(Mandatory=$true)]$RackPass, [Parameter()]$IntensiveUser, [Parameter()]$IntensivePass, [Parameter(Mandatory=$true)]$AdminPass, [Parameter(Mandatory=$true)]$Connection, [Parameter()]$DomainUser = "", [Parameter()]$DomainPass = "", [Parameter()][switch]$SkipCoreStatus, [Parameter()][switch]$ContinueOnPingError ) $Output = New-Object PSObject -Property @{ OnlineStatus = "" WinRMDate = "" PingDNS = "" PingIP = "" Port139 = "" Port445 = "" Port5986 = "" ValidCredentials = "" ValidDomain = "" ValidUsername = "" ValidPassword = "" } #Skip the core status check if using this flag If (-NOT $SkipCoreStatus) { $Connection.OnlineStatus = Test-CoreStatus -Status $status } ELSE { $Connection.OnlineStatus = $true } #Test the server status and if WinRM could be enabled (via online date) $Connection.WinRMDate = Test-WinRMDate -dateonline $dateonline $Connection.WinRMDate = $true $PingResult = Test-PingGroup -IP $IP -ServerName $ServerName -Domain $Domain #Update our output table with the results $Connection.PingDNS = $PingResult.DNS $Connection.PingIP = $PingResult.IP #Run port tests $PortResults = Test-TCPPortGroup -IP $IP #Update our output table with the results $Connection.Port139 = $PortResults.139 $Connection.Port445 = $PortResults.445 $Connection.Port5986 = $PortResults.5986 IF ($PortResults.445) { $Params = @{ IP = $IP; ServerName = $ServerName; RackPass = $RackPass; IntensiveUser = $Global:IntensiveUser; IntensivePass = $Global:IntensivePass; AdminPass = $AdminPass; DomainUser = $DomainUser; DomainPass = $DomainPass; Domain = $Domain } $ValidCredentials = Test-Credentials @Params #Update our output table with the results $Connection.ValidCredentials = $ValidCredentials.ValidCredentials $Connection.ValidDomain = $ValidCredentials.ValidDomain $Connection.ValidUsername = $ValidCredentials.ValidUsername $Connection.ValidPassword = $ValidCredentials.ValidPassword } Return $Connection } Function Test-WinRM { Param ( [Parameter(Mandatory=$true)]$WHAMConnectivity, [Parameter()][switch]$SkipCoreStatus ) $Params = @{ WHAMConnectivity = $WHAMConnectivity } If ($SkipCoreStatus) { $Params.Add("SkipCoreStatus", $true) } $SharedResults = Get-WHAMConnectionResults @Params $Success = $SharedResults.Success $ErrorReason = $SharedResults.ErrorReason #Check WinRM Date IF ($Success -ne $false) { IF ($WHAMConnectivity.WinRMDate -ne $true) { $ErrorReason += "WINRM not enabled " $Success = $false } } #Check DNS ping IF ($Success -ne $false) { IF ($WHAMConnectivity.PingDNS -ne $true) { $ErrorReason += "DNS ping failed " $Success = $false } } IF ($Success -ne $false) { $WinRMParams = @{ Username = $WHAMConnectivity.ValidUsername; Password = $WHAMConnectivity.ValidPassword; UserDomain = $WHAMConnectivity.ValidDomain; ComputerDomain = $Computer.Domain; ServerName = $Computer.ServerName; } $WinRMConnection = Test-WinRMConnection @WinRMParams $Success = $WinRMConnection.WinrmSuccess } IF ($Success -ne $false) { $success = $true } $Output = New-Object PSObject -Property @{ WinRMErrorReason = $ErrorReason WinRMSuccess = $Success } Return $Output } Function Test-PSExec { Param ( [Parameter(Mandatory=$true)]$WHAMConnectivity, [Parameter(Mandatory=$true)]$IP, [Parameter()][switch]$SkipCoreStatus ) $Params = @{ WHAMConnectivity = $WHAMConnectivity } If ($SkipCoreStatus) { $Params.Add("SkipCoreStatus", $true) } $SharedResults = Get-WHAMConnectionResults @Params $Success = $SharedResults.Success $ErrorReason = $SharedResults.ErrorReason IF ($Success -ne $false) { $PsExecParams = @{ IP = $IP; Username = "$($WHAMConnectivity.ValidDomain)\$($WHAMConnectivity.ValidUsername)"; Password = $WHAMConnectivity.ValidPassword; PsexecPath = "$ENV:tmp"; } $PSTest= "" $PSTest = Test-PSExecConnection @PsExecParams If (-NOT $PSTest.PSexeConnected ) { $Success = $false $ErrorReason += "$($PSTest.Lastexitcode):$($PSTest.ErrorReason)" } } IF ($Success -ne $false) { $success = $true } $Output = New-Object PSObject -Property @{ PsexecErrorReason = $ErrorReason PsexecSuccess = $Success } Return $Output } #Creates the connection PSObject that is used through out WHAM Function New-WHAMConnectionPSObject { Param( [Parameter(mandatory=$true)]$Computer ) #Create PSObject $Connection = New-Object PSObject -Property @{ ServerName = $Computer.Servername OnlineStatus = "-" Status = $Computer.Status WinRMDate = "Not checked" Account = $Computer.Account IP = $Computer.IP Domain = $Computer.Domain ConnectionType = "-" ConnectionFailure = "-" ConnectionFailureReason = "-" ScriptFailure = "-" ScriptFailureReason = "-" ScriptFailureCount = "-" PingDNS = "-" PingIP = "-" Port139 = "-" Port445 = "-" Port5986 = "-" ValidCredentials = "-" ValidDomain = "-" ValidUsername = "-" ValidPassword = "-" PSExecConnection = "-" PSEXECFailure = "-" PSExecReturnCode = "-" PSExecSuccess ="-" PSExecErrorReason = @() ReturnedData = "-" WinRMConnection = "-" WinRMSuccess = "-" WinRMErrorReason = @() LastPsexecConnection = "-" LastPSExecExitCode ="-" LastWinRMConnection = "-" } Return $Connection } Function Get-WHAMConnectionResults { <# .Synopsis .DESCRIPTION .EXAMPLE .OUTPUTS #> Param ( [Parameter(Mandatory=$true)]$WHAMConnectivity, [Parameter()][switch]$SkipCoreStatus ) $ErrorReason = @() $Success = "" IF ($SkipCoreStatus -ne $true) { #Check the CORE status IF ($WHAMConnectivity.OnlineStatus -ne $true) { $ErrorReason += "CORE status is $($WHAMConnectivity.Status)" $Success = $false } } IF ($WHAMConnectivity.PingIP -ne $true) { $ErrorReason += "Not responding to ping" #this is not a hard fail, we just log we cannot ping #$Success = $false } #Update the error reason if 139 is closed but continue IF ($WHAMConnectivity.Port139 -ne $true) { #this is not a hard fail, we just log we cannot connect to 139 $ErrorReason += "Port TCP 139 not open" } #Check port 445 IF ($Success -ne $false) { IF ($WHAMConnectivity.Port445 -ne $true) { $ErrorReason += "Port TCP 445 not open" $Success = $false } } If ($WinRM) { IF ($Success -ne $false) { IF ($WHAMConnectivity.Port5986 -ne $true) { $ErrorReason += "Port TCP 5986 not open" $Success = $false } } } #Check valid credentials IF ($Success -ne $false) { IF ($WHAMConnectivity.ValidCredentials -eq "None") { $ErrorReason += "No valid credentials" $Success = $false } } #If nothing has set success to false, set it as true If ($Success -ne $false) { $Success = $true } $Output = New-Object PSObject -Property @{ ErrorReason = $ErrorReason Success = $Success } Return $Output } Function Get-WHAMConnectionType { <# .Synopsis Takes data from Test-WHAMConnectivity to determine if PSEXEC or WINRM is working .DESCRIPTION runs lots of checks to ensure psexec or winrm is working and finds correct credentials .EXAMPLE .OUTPUTS PSObject called $output with following: Connection - if server could be pinged ConnectionType - WINRM / PSEXEC / Fail ConnectionFailureReason - Reason of a connection failure OnlineStatus - true/false depending if connectable status from CORE WinRMDate - true/false depending if WinRM date PingDNS - true/false on ping result via DNS PingIP - true/false on ping result via IP Port139 - true/false on port check result Port445 - true/false on port check result Port5986 - true/false on port check result ValidCredentials - type of valid credentials found - rack/intensive/customer/admin ValidDomain - valid domain for user credentials ValidUsername - valid username ValidPassword - valid password #> Param ( [Parameter(Mandatory=$true)]$Computer, [Parameter()][switch]$SkipCoreStatus, [Parameter()][switch]$ContinueOnPingError ) $WHAMConnectivity = New-WHAMConnectionPSObject -Computer $computer $ComputerParamValidated = Get-WHAMParamValidation -Computer $computer If (-NOT $ComputerParamValidated.WHAMValid) { $WHAMConnectivity.ConnectionType = "NONE" $WHAMConnectivity.ConnectionFailure = $true $WHAMConnectivity.ConnectionFailureReason = $ComputerParamValidated.WHAMInvalidReason } else { If ($($Computer.date_online).length -eq 0) { $date_online = "" } ELSE { $date_online = $Computer.date_online } $Params = @{ #dateonline = $Computer.date_online; status = $Computer.status; IP = $Computer.IP; ServerName = $Computer.ServerName; Domain = $Computer.Domain; RackPass = $Computer.RackPass; IntensiveUser = $Global:IntensiveUser; IntensivePass = $Global:IntensivePass; AdminPass = $Computer.AdminPass; DomainUser = $Computer.DomainUser; DomainPass = $Computer.DomainPass; Connection = $WHAMConnectivity } #Do generic connection tests $WHAMConnectivity = Test-WHAMConnectivity @Params #Pass these to our test-winrm function $Params = @{ WHAMConnectivity = $WHAMConnectivity } If ($SkipCoreStatus) { $Params.Add("SkipCoreStatus", $true) } $WinrmResults = Test-Winrm @Params $WHAMConnectivity.WinRMSuccess = $WinrmResults.WinRMSuccess $WHAMConnectivity.WinRMErrorReason = $WinrmResults.WinRMErrorReason #Pass these to our test-PSExec function $Params = @{ WHAMConnectivity = $WHAMConnectivity; IP = $Computer.IP } If ($SkipCoreStatus) { $Params.Add("SkipCoreStatus", $true) } $PsexecResults = Test-Psexec @Params $WHAMConnectivity.PsexecSuccess = $PsexecResults.PsexecSuccess $WHAMConnectivity.PsexecErrorReason = $PsexecResults.PsexecErrorReason #Update our PSObject with the connection type for this server IF ($WinrmResults.WinRMSuccess) { $WHAMConnectivity.ConnectionType = "WINRM" $WHAMConnectivity.ConnectionFailure = $false } ELSEIF($PsexecResults.PsexecSuccess) { $WHAMConnectivity.ConnectionType = "PSEXEC" $WHAMConnectivity.ConnectionFailure = $false } ELSE { $WHAMConnectivity.ConnectionType = "NONE" if (($WHAMConnectivity.PsexecErrorReason | Measure-Object).count -eq 1) { $PsexecError = $($WHAMConnectivity.PsexecErrorReason) } else { #$total = $($PsexecResults.PsexecErrorReason).count $PsexecError= $($PsexecResults.PsexecErrorReason[0]) } if (($WHAMConnectivity.WinRMErrorReason | Measure-Object).count -eq 1) { $WinRMError = $($WHAMConnectivity.WinRMErrorReason) } else { #$total = $($WHAMConnectivity.WinRMErrorReason).count $WinRMError = $($WHAMConnectivity.WinRMErrorReason[0]) } $WHAMConnectivity.ConnectionFailureReason = "PSEXEC: $PsexecError | WINRM: $WinRMError" $WHAMConnectivity.ConnectionFailure = $true } } Return $WHAMConnectivity } <# .Synopsis Short description .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .OUTPUTS Output from this cmdlet (if any) .NOTES General notes #> Function Test-TCPPort { Param ( #IP of device being tested. [Parameter(Mandatory=$true, Position=0)] [String]$IP, # Port to test. [Parameter(Mandatory=$true, Position=1)] [Int]$Port, # Timeout Value for test [Parameter(Position=2)] [Int]$Timeout = 1000 ) [Switch]$PortTest = $Null $ticksPerMilliSecond = 10000 $client = New-Object net.sockets.tcpclient $client.BeginConnect($IP,$port,$null,$null) > $null if ($client.Connected) {$client.Close();return $true} $startTimeout = Get-Date while ((Get-Date).Ticks - $startTimeout.Ticks -le $timeout * $ticksPerMilliSecond) { if ($client.Connected) { $client.Close() return $true } } $client.Close() return $false } <# .Synopsis Short description .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .OUTPUTS Output from this cmdlet (if any) .NOTES General notes #> Function Test-TCPPortGroup { Param ( #IP of device being tested. [Parameter(Mandatory=$true, Position=0)] [String]$IP ) [Array]$PortTests = $Null $PortTests = [PSCustomObject]@{ 5986 = Test-TCPPort -IP $IP -Port 5986 445 = Test-TCPPort -IP $IP -Port 445 139 = Test-TCPPort -IP $IP -Port 139 } Return $PortTests } #$PortTests = Test-TCPPortGroup -IP "127.0.0.1" Function Test-PSExecConnection { Param ( #IP of device being tested. [Parameter(Mandatory=$true)][String]$IP, [Parameter(Mandatory=$true)]$Username, [Parameter(Mandatory=$true)]$Password, [Parameter()]$PsexecPath = $ENV:tmp ) $PSExecParams = @{ IP = $IP; Username = $Username; Password = $Password; PsexecPath = $PSEXECPath; Command = "powershell (get-host).version.major"; } $result = Connect-PSExec @PSExecParams #make sure the exit code is 0 (success) and then check the result as it will be the Powershell version IF(($result.PSEXECResult -gt 1 -AND ($result.lastexitcode -eq 0))) { $Success = $True } Else { $Success = $False IF ($result.lastexitcode -eq 0) { $result.ErrorReason = "Powershell version is less then 2" } } $Test = New-Object PSObject $Test | Add-Member -type NoteProperty -Name "IP" -Value $result.IP -Force $Test | Add-Member -type NoteProperty -Name "ErrorReason" -Value $result.ErrorReason -Force $Test | Add-Member -type NoteProperty -Name "lastexitcode" -Value $result.LASTEXITCODE -Force $Test | Add-Member -type NoteProperty -Name "TestOutput" -Value $result.PSEXECResult -Force $Test | Add-Member -type NoteProperty -Name "PSexeConnected" -Value $Success -Force Return $Test } Function Connect-PSExec { Param ( #IP of device being tested. [Parameter(Mandatory=$true,Position=0)][string]$IP, [Parameter(Mandatory=$true,Position=1)]$Username, [Parameter(Mandatory=$true,Position=2)]$Password, [Parameter(Mandatory=$true,Position=3)]$Command, [Parameter(Mandatory=$true)]$PsexecPath = $ENV:tmp, [Parameter()][switch]$LogOutput ) If ($LogOutput) { #$ErrorLogPath = "$Global:WHAMLogPath\PSEXEC_error_$Ip.txt" #New-Item -Path $ErrorLogPath -Force -ItemType file -ErrorAction SilentlyContinue| Out-Null } $Command = "cmd.exe /c $Command" [string]$Result = & $PsexecPath\psexec.exe /acceptEula \\$IP -u $Username -p $Password -h cmd.exe /c $command 2> $ErrorLogPath If ($LASTEXITCODE -eq 6) { Start-Sleep -Seconds 5 [string]$Result = & $PsexecPath\psexec.exe /acceptEula \\$IP -u $Username -p $Password -h cmd.exe /c $command 2> $ErrorLogPath } switch ($LASTEXITCODE) { "0" { $Success = $true $ErrorReason = "" } "1326" { $Success = $false $ErrorReason = "unknown user name or bad password" } Default { $Success = $false $ErrorReason = "Unspecified PSEXEC error" } } $Output = New-Object PSObject $Output | Add-Member -type NoteProperty -Name "IP" -Value $IP $Output | Add-Member -type NoteProperty -Name "ErrorReason" -Value $ErrorReason $Output | Add-Member -type NoteProperty -Name "lastexitcode" -Value $LASTEXITCODE $Output | Add-Member -type NoteProperty -Name "PSEXECResult" -Value $Result $Output | Add-Member -type NoteProperty -Name "PSexeConnected" -Value $Success Return $Output } Function Start-WHAMJobs { Param ( [Parameter(Mandatory=$true)]$Computers, [Parameter(Mandatory=$true)][scriptblock]$JobScriptblock, [Parameter(Mandatory=$true)]$JobLimit, [Parameter()]$IntensiveUser, [Parameter()]$IntensivePass, [Parameter()]$DownloadFile ) #This is the counter that counts the array we are using $Arr = 0 #Total number of jobs $totalJobs = $null $TotalJobCount = $($Computers | Measure-Object).Count If ($TotalJobCount -lt 1) { write-host "No jobs to run!" -ForegroundColor red } ELse { write-host " " write-host "Creating $TotalJobCount jobs with a limit of $JobLimit simultaneous jobs" $currentjob = 1 do { Write-Progress -Activity "Creating $TotalJobCount jobs with a limit of $JobLimit simultaneous jobs" ` -PercentComplete (($currentjob/$totaljobcount) * 100) -CurrentOperation "Waiting for next free slot..." -Status "Processing job $arr of $totaljobcount" #Find the total number of running jobs $TotalRunningJobs = (get-job | Where-Object {$_.State -eq "Running"}).Count #If number of running jobs is less then our limit, we can start some more If ($TotalRunningJobs -lt $JobLimit) { #Update our var list with the current computer $VarList = @( $($Computers[$arr]), $Global:RootPath $Global:WHAMLogPath $Global:WHAMExportPath $IntensiveUser $IntensivePass $Global:FolderName $DownloadFile ) #Grab the server name $name = $($Computers[$arr].Servername) #Remove any old jobs with the same name If ((Get-Job)) { Remove-Job -Name $name -Force -ErrorAction SilentlyContinue } Start-Job -Name $Name -ScriptBlock $JobScriptblock -ArgumentList $VarList | Out-Null #Increment our counters $Arr++ $totalJobs ++ <# write-host " " write-host "$Arr" -ForegroundColor Yellow -NoNewline write-host "/" -NoNewline write-host "$TotalJobCount`: " -ForegroundColor Cyan -NoNewline write-host "Starting job on $name" `#> #write-host "TotalRunningJobs -lt Limit" $currentjob++ If (((get-job | Where-Object {$_.State -eq "Running"}).Count -eq $limit) -AND ($arr -ne ($Limit -1))) { #write-host "Waiting for next free slot..." } } } until ($TotalJobs -eq $($Computers | Measure-Object).Count) Write-Progress "Complete" -completed write-host " " write-host "All jobs created!" } } Function Get-WHAMConnectivityJobResults { Param ( [Parameter(Mandatory=$true)]$Computers ) #Loop through the servers and get the results, updating the computer object ForEach ($Computer in $Computers) { $connection = "" #Run the function to get the job results $connection = Get-WHAMJobResult -Name $Computer.ServerName #If the job failed then create the object and update it If (($connection -eq "job error") -OR ($connection -eq "job not found")) { #If the job fails we need to create the PSObject $ConnectionPSO = New-Object PSObject -Property @{ ConnectionFailure = $true ConnectionFailureReason = $connection } $Computer | Add-Member -type NoteProperty -Name "connection" -Value $ConnectionPSO -Force } ELSE { #Add the results to the connections property $Computer | Add-Member -type NoteProperty -Name "Connection" -Value $connection -Force #Write-Host $($Computer.connection | Out-String) -ForegroundColor yellow } } #Return our computer array Return $Computers } Function Get-WHAMJobStatuses { <# .Synopsis Make sure all jobs are finished .DESCRIPTION Make sure all jobs are finished within set number of seconds and if they are not, forcibly close them .EXAMPLE Get-JobStatuses .OUTPUTS Returns $joberror as true if it fails .NOTES General notes #> Param ( [Parameter()]$timeout = 60, #Default 60 second timeout [Parameter()][Switch]$SkipKill #Skips killing the jobs for testing ) $JobsToRetreive = (get-job | Where-Object {$_.HasMoreData -eq "True" -AND $_.Name -notlike "Job*"}).Count write-host " " write-host "Retrieving $JobsToRetreive jobs with a maximum timeout of $Timeout seconds" $Totaljobs = $JobsToRetreive $count = 0 do { #Jobcount $CurrentJobs = $(get-job | Where-Object {$_.State -ne "Completed"}).Count #Write to the screen our ............'s #write-host "$count.." -NoNewline $PercentComplete = ($count/$timeout) * 100 If ($PercentComplete -gt 100) { $PercentComplete = 100 } Write-Progress -Activity "Retrieving $JobsToRetreive jobs with a maximum time out of $Timeout seconds"` -PercentComplete $PercentComplete -Status "Currently waiting on $CurrentJobs jobs" -SecondsRemaining ($timeout - $count) #Pause for 1 second sleep 1 #If we have been going for the timeout limit, flag the job error If ($Count -gt $timeout) { write-host "." write-host "Error: Jobs have not finished after $timeout seconds - this is probably a PSEXEC error. WHAM will automatically try again" -ForegroundColor Yellow #Flag our error and break the loop $joberror = $true break } #Add to our counter $count ++ } #Keep looping until all jobs are in completed state Until (((get-job | Where-Object {$_.State -ne "Completed"}).Count) -eq 0) Write-Progress "Complete" -completed If ($JobError) { #If we detected a job, get the failed job $failedjobs = get-job | Where-Object {$_.State -ne "Completed"} #$failedjobs.Name If (-not $SkipKill) { #Now kill the jobs Get-Job | Stop-Job } #Return the failed jobs return $failedjobs } write-host " " } Function Get-WHAMJobResult { <# .Synopsis Get output from job .DESCRIPTION Gets data from job and then removes it .EXAMPLE Get-JobResult -Name 500111-Server .OUTPUTS The output from the job .NOTES General notes #> Param( [Parameter(Mandatory=$true)]$Name ) Try { #Check job has finished $Job = Get-Job -Name $Name -ErrorAction Stop If ($Job.State -eq "Completed") { #Get the result for this job $Result = Get-Job -Name $Name | Receive-Job } ELSE { $Result = "job error" } Return $Result } Catch { $Result = "job not found" Return $Result } Finally { #Remove the job Remove-Job -Name $Name -Force -ErrorAction SilentlyContinue } } Function Test-JobScriptblock { $scriptblock = { $sleepytime = Get-Random -Minimum 5 -Maximum 15 Sleep $sleepytime } Return $scriptblock } Function Get-WHAMConnectionTestJobScriptblock { Param( [Parameter()][switch]$SkipCoreStatus, [Parameter()][switch]$ContinueOnPingError ) $Params = "-Computer `$Computer" If ($SkipCoreStatus) { $Params = "$Params -SkipCoreStatus" } If ($ContinueOnPingError) { $Params = "$Params -ContinueOnPingError" } #scriptblock we use to run the jobs $ScriptblockString = @" #Convert our arguments from the job back to proper variables `$Computer = `$args[0] `$RootPath = `$args[1] `$Global:WHAMLogPath = `$args[2] `$Global:WHAMExportPath = `$args[3] `$IntensiveUser = `$args[4] `$IntensivePass = `$args[5] `$FolderName = `$args[6] #Set the root path to use in the job `$RootPath = `$RootPath.Replace("Microsoft.PowerShell.Core\FileSystem::","") #import the module we will use into our job scope Import-Module "`$ENV:tmp\WHAM_Execution_Module.psm1" -DisableNameChecking -scope Global `$result = "" #capture the result from the connection testing function `$Result = Get-WHAMConnectionType $Params Return `$Result "@ #Convert it to a script block $JobScriptblock = [Scriptblock]::Create($ScriptblockString) Return $JobScriptblock } Function Get-WHAMParamValidation { <# .Synopsis Check Server data meets requirements for WHAM e.g. nothing missing. #> Param ( [Parameter(Mandatory=$true)]$Computer ) [String]$WHAMInvalidReason = $null [switch]$WHAMValid = $true $WHAMValid = $true If ($($computer.IP).length -eq 0) { $WHAMValid = $false $WHAMInvalidReason = $WHAMInvalidReason + "IP null in CORE " } ElseIf ( -NOT ([bool]($($computer.IP) -as [ipaddress]))) { $WHAMValid = $false $WHAMInvalidReason = $WHAMInvalidReason + "IP invalid in CORE " } If ($($computer.ServerName).length -eq 0) { $WHAMValid = $false $WHAMInvalidReason = $WHAMInvalidReason + "ServerName null in CORE " } <# If ($($computer.dateonline).length -eq 0) { $WHAMValid = $false $WHAMInvalidReason = $WHAMInvalidReason + "dateonline='Null' " } #> If ($($computer.Status).length -eq 0) { $WHAMValid = $false $WHAMInvalidReason = $WHAMInvalidReason + "Status null in CORE " } If ($($computer.RackPass).length -eq 0) { $WHAMValid = $false $WHAMInvalidReason = $WHAMInvalidReason + "RackPass null in CORE " } If ($($computer.AdminPass).length -eq 0) { $WHAMValid = $false $WHAMInvalidReason = $WHAMInvalidReason + "AdminPass null in CORE " } $Output = New-Object PSObject -Property @{ Server = $Computer.Servername WHAMInvalidReason = $WHAMInvalidReason WHAMValid = $WHAMValid } Return $Output } Function Run-WHAMConnectionTests { Param( [Parameter(Mandatory=$true)]$Computers, [Parameter(Mandatory=$true)]$JobLimit, [Parameter()][switch]$SkipCoreStatus, [Parameter()][switch]$ContinueOnPingError, [Parameter()]$IntensiveUser, [Parameter()]$IntensivePass, [Parameter()]$DomainUser, [Parameter()]$DomainPass ) $JobScriptblockParams = @{ } #$ContinueOnPingError = $true If ($SkipCoreStatus) { $JobScriptblockParams.Add("SkipCoreStatus", $true) } If ($ContinueOnPingError) { $JobScriptblockParams.Add("ContinueOnPingError", $true) } #Start the jobs to test the connections to the servers $JobScriptblock = Get-WHAMConnectionTestJobScriptblock @JobScriptblockParams $Params = @{ Computers = $Computers JobLimit = $JobLimit JobScriptblock = $JobScriptblock IntensiveUser = $IntensiveUser IntensivePass = $IntensivePass } #Start the jobs - the limit is set as a paramater to the script Start-WHAMJobs @Params sleep 2 #Check the status of the jobs then return them $failedjobs = Get-WHAMJobStatuses #Get the job results and save them all back as our computer object $ComputersRecieved = Get-WHAMConnectivityJobResults -Computers $Computers Display-ConnectionTestSummary -Computers $ComputersRecieved #Save the object back as its original one $Computers = $ComputersRecieved Return $Computers } Function Get-WHAMScriptJobScriptblock { Param( [Parameter()]$DownloadFile ) $ScriptblockString = @" #import our arguements passed through to the job `$Computer = `$args[0] `$RootPathinJob = `$args[1] `$Global:WHAMLogPath = `$args[2] `$Global:WHAMExportPath = `$args[3] `$IntensiveUser = `$args[4] `$IntensivePass = `$args[5] `$FolderName = `$args[6] `$DownloadFile = `$args[7] #Global root is a PITA and will sometimes add this so we will remove it `$RootPathinJob = `$RootPathinJob.Replace("Microsoft.PowerShell.Core\FileSystem::","") #Set some path variables `$Servername = `$Computer.Servername `$LocalScriptPath = "`$Global:WHAMExportPath\WHAM-Script.ps1" `$LocalComputerXMLPath = "`$Global:WHAMExportPath\`$Servername-computer.xml" `$DriveName = `$Computer.Number `$PS1PathRemote = "`$(`$DriveName):\rs-pkgs\WHAM-Script.ps1" `$PS1PathLocal = "C:\rs-pkgs\WHAM-Script.ps1" `$XMLPath = "`$(`$DriveName):\rs-pkgs\wham_export.xml" #create our output PSObject `$Output = New-Object PSObject -Property @{ ScriptFailure = "" ScriptFailureReason = "" ReturnedData = "" Servername = `$computer.servername } #Import the connection module Import-Module "`$ENV:tmp\WHAM_Execution_Module.psm1" -Force -DisableNameChecking -scope Global #new-psdrive doesnt like "." as domain so we just dont use it If (`$(`$Computer.Connection.ValidDomain) -eq ".") { `$Username = "`$(`$Computer.Connection.ValidUsername)" } else { `$Username = "`$(`$Computer.Connection.ValidDomain)\`$(`$Computer.Connection.ValidUsername)" } #set our credentials `$Password = `$(`$Computer.Connection.ValidPassword) | ConvertTo-SecureString -asPlainText -Force `$Credentials = New-Object System.Management.Automation.PSCredential(`$Username,`$Password) `$Path = "\\`$(`$Computer.IP)\c`$" #create a new ps drive using the credentials Try { #clean up any stale net use paths `$x = & net use `$Path /d 2> `$null Remove-PSDrive `$DriveName -ErrorAction SilentlyContinue | Out-Null `$x = New-PSDrive -PSProvider FileSystem -Name `$DriveName -Root `$Path -Credential `$Credentials -ErrorAction Stop | Out-Null } Catch { #Add-Content -Path `$Logfile -Value "failed to map PSdrive" `$output.ScriptFailure = `$true `$output.ScriptFailureReason = "Job error: Failed to map PSdrive" return `$output break } #Ensure RS-PKGS folder is present mkdir "`$(`$DriveName):\rs-pkgs" -ErrorAction SilentlyContinue | Out-Null #Delete files from last execution #Remove-Item `$PS1PathRemote -Force -ErrorAction SilentlyContinue | Out-Null #Remove-Item `$XMLPath -Force -ErrorAction SilentlyContinue | Out-Null #Copy script to target server Copy-Item `$LocalScriptPath "`$(`$DriveName):\rs-pkgs" -force Copy-Item `$LocalComputerXMLPath "`$(`$DriveName):\rs-pkgs\computer.xml" -force #If script is not found, fail IF (-NOT (Test-Path `$PS1PathRemote)) { #Add-Content -Path `$Logfile -Value "failed to copy file" `$output.ScriptFailure = `$true `$output.ScriptFailureReason = "Job error: Failed to copy PS1 file" return `$output break } #Download file will be an array of all the zips we need to copy If (`$DownloadFile) { ForEach (`$file in `$downloadfile) { #Set folder paths `$ServerZipPath = "`D:\DFS-Public\WHAM\Download\`$file" `$RemoteZipPath = "`$(`$DriveName):\rs-pkgs\WHAM" #copy the file and make sure it is present mkdir "`$(`$DriveName):\rs-pkgs\WHAM" -Force | Out-Null Copy-Item `$ServerZipPath "`$(`$DriveName):\rs-pkgs\WHAM" -force if (-NOT (Test-Path `$RemoteZipPath\`$file)) { `$output.ScriptFailure = `$true `$output.ScriptFailureReason = "Job Error: Failed to copy `$ServerZipPath to remote server" return `$output `$joberror = `$true break } } } #break only break the foreach loop so now break out the script if found error if (`$joberror){break} #Connection via WINrm, if chosen method If (`$computer.connection.ConnectionType -like "WinRM") { #set our params `$WinRMParams = @{ Username = `$Computer.Connection.ValidUsername; Password = `$Computer.Connection.ValidPassword; Userdomain = `$Computer.Connection.Validdomain; ComputerDomain = `$Computer.Domain; ServerName = `$Computer.ServerName; Scriptblock = {powershell -executionpolicy bypass C:\rs-pkgs\wham-script.ps1} } #run the connection command `$Result = Connect-Winrm @WinRMParams #if we get an error, fail If (`$Result.WinRMError) { `$output.ScriptFailure = `$true `$output.ScriptFailureReason = `$Result.WinRMErrorReason return `$output break } } ELSE { #check if we are using domain credentials, if we are pass them through If ((`$computer.connection.ValidCredentials -eq "Customer") -OR (`$computer.connection.ValidCredentials -eq "Intensive")) { `$Command = "powershell -executionpolicy bypass -File C:\rs-pkgs\wham-script.ps1 -DomainUser `$(`$computer.connection.ValidDomain)\`$(`$computer.connection.ValidUsername) -DomainPassword `$(`$computer.connection.ValidPassword)" } else { #if no creds run normally `$Command = "powershell -executionpolicy bypass C:\rs-pkgs\wham-script.ps1" } `$PSEXECPath = "`$env:tmp" #set parmeters `$PAExecParams = @{ IP = `$Computer.IP; Username = "`$(`$Computer.Connection.Validdomain)\`$(`$Computer.Connection.ValidUsername)"; Password = "`$(`$Computer.connection.ValidPassword)"; Command = `$Command; PsexecPath = `$PSEXECPath; } #run the psexec connection `$result = Connect-PSExec @PAExecParams #if result code is not 0, psexec errored If (`$result.lastexitcode -ne 0) { `$output.ScriptFailure = `$true `$output.ScriptFailureReason = "Job error: psexec exit code is `$(`$result.lastexitcode)" return `$output break } #if result is not script ok, somnething went boom IF (`$result.PSEXECResult -ne "Script OK") { `$output.ScriptFailure = `$true `$output.ScriptFailureReason = "Script returned: `$(`$result.PSEXECResult)" return `$output break } } #Make sure our path is still present If (-NOT (Test-Path "`$(`$DriveName):\rs-pkgs")) { `$output.ScriptFailure = `$true `$output.ScriptFailureReason = "Job error: PSDrive path not present after script execution" return `$output break } #without this sleep, the xml doesnt have time to create and causes the script to fail sleep 5 #Look for the exported XML from the server IF (-NOT (Test-Path `$XMLPath)) { `$output.ScriptFailure = `$true `$output.ScriptFailureReason = "Job error: Export XML is not present on remote server" return `$output break } #try and import the xml Try { `$xml = Import-Clixml `$XMLPath } Catch { `$output.ScriptFailure = `$true `$output.ScriptFailureReason = "Job error: failed to import xml from remote server" return `$output break } #delete the files we created Remove-Item "`$(`$DriveName):\rs-pkgs\WHAM-Script.ps1" -Force -ErrorAction SilentlyContinue | Out-Null Remove-Item "`$(`$DriveName):\rs-pkgs\wham_export.xml" -Force -ErrorAction SilentlyContinue | Out-Null Remove-Item "`$(`$DriveName):\rs-pkgs\computer.xml" -Force -ErrorAction SilentlyContinue | Out-Null #if we didnt set a failure, it must of worked right?! If (`$output.ScriptFailure -ne `$true) { `$output.ScriptFailureReason = "none" `$output.ScriptFailure = `$false } `$output.ReturnedData = `$xml return `$output "@ #Convert it to a script block $JobScriptblock = [Scriptblock]::Create($ScriptblockString) Return $JobScriptblock } Function Get-WHAMScriptJobResults { Param ( [Parameter(Mandatory=$true)]$Computers ) #Loop through the servers and get the results, updating the computer object ForEach ($Computer in $Computers) { $ScriptResult = "" #Run the function to get the job results $ScriptResult = Get-WHAMJobResult -Name $Computer.ServerName #If the job failed then create the object and update it If (($ScriptResult -eq "job error") -OR ($ScriptResult -eq "job not found")) { #If the job fails we need to create the PSObject $ConnectionPSO = New-Object PSObject -Property @{ ScriptFailure = $true ScriptFailureReason = $ScriptResult } $Computer | Add-Member -type NoteProperty -Name "WHAMScript" -Value $ConnectionPSO -Force } ELSE { #Add the results to the connections property $Computer | Add-Member -type NoteProperty -Name "WHAMScript" -Value $ScriptResult -Force #Write-Host $($Computer.connection | Out-String) -ForegroundColor yellow } } Return $Computers } Function Run-WHAMScript { Param ( [Parameter(mandatory=$true)]$computers, [Parameter(mandatory=$true)]$Joblimit, [Parameter()]$IntensivePass, [Parameter()]$IntensiveUser, [Parameter()]$ScriptTimeout, [Parameter()]$DownloadFile ) $scriptcomputers = @() #filter the computer objects just by the ones that are successful for connection test ForEach ($Computer in $Computers) { IF (-NOT $COmputer.connection.connectionfailure) { $scriptcomputers += $Computer } $ExportComputer = New-Object PsObject $computer.psobject.properties | % { $ExportComputer | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value } $ExportComputer = Remove-Passwords -computer $ExportComputer $ExportComputer | Export-Clixml "$Global:WHAMExportPath\$($Computer.Servername)-computer.xml" -Force #$Computer | Export-Clixml "$Global:WHAMExportPath\$($Computer.Servername)-computer.xml" -Force } $JobScriptblock = Get-WHAMScriptJobScriptblock -DownloadFile $DownloadFile $Params = @{ Computers = $scriptcomputers JobLimit = $JobLimit JobScriptblock = $JobScriptblock IntensiveUser = $IntensiveUser IntensivePass = $IntensivePass DownloadFile = $Downloadfile } Start-WHAMJobs @Params #Check the status of the jobs and closes them after 60 seconds $failedjobs = Get-WHAMJobStatuses -Timeout $ScriptTimeout #Get the job results and save them all back as our computer object $ComputersRecieved = Get-WHAMScriptJobResults -Computers $scriptcomputers $MergedComputers = Set-WHAMMergeScriptComputers -computers $Computers -computersrecieved $ComputersRecieved #Delete the computer XML we exported for the job ForEach ($computer in $MergedComputers) { Remove-Item "$Global:WHAMExportPath\$($Computer.Servername)-computer.xml" -Force -ErrorAction SilentlyContinue | Out-Null } return $MergedComputers } Function Set-WHAMMergeScriptComputers { Param ( [Parameter()]$computers, [Parameter()]$ComputersRecieved ) $newcomputers = @() ForEach ($Computer in $computers) { #If the servername is in the computersrecieved list, it means the script ran so we need to update the computer object with the new one in $ComputersRecieved If($ComputersRecieved.servername -contains $computer.servername) { ForEach ($ScriptServer in $ComputersRecieved) { If ($COmputer.Servername -eq $ScriptServer.ServerName) { $Computer = $Scriptserver $newcomputers += $Scriptserver continue } } } ELSE { #If the servername is NOT in the computersrecieved list, the script didnt run so we just add it from computers to the new array $newcomputers += $computer } } If (($newcomputers | measure-object).count -ne ($computers | measure-object).count) { write-host "error in Set-WHAMMergeScriptComputers" -ForegroundColor Red } Return $newcomputers } Function Run-WHAMConnectionTestFailures { Param( [Parameter(Mandatory=$true)]$Computers, [Parameter(Mandatory=$true)]$JobLimit, [Parameter()][switch]$SkipCoreStatus, [Parameter()][switch]$ContinueOnPingError, [Parameter()]$IntensiveUser, [Parameter()]$IntensivePass ) sleep 5 $FailedComputers = $Computers | Where-Object {$_.connection.ConnectionFailure -eq $true} If (($FailedComputers | Measure-Object).count -gt 0) { write-host " " write-host "There are $(($FailedComputers | Measure-Object).count) failed jobs, restarting them now" $Params = @{ Computers = $FailedComputers; JobLimit = $JobLimit; } If ($SkipCoreStatus) { $Params.Add("SkipCoreStatus", $true) } $ComputersRecieved = Run-WHAMConnectionTests @Params $comps = Set-WHAMMergeScriptComputers -ComputersRecieved $ComputersRecieved -computers $computers return $comps } ELSE { return $Computers } } Function Run-WHAMScriptFailures { Param( [Parameter(Mandatory=$true)]$Computers, [Parameter(Mandatory=$true)]$JobLimit, [Parameter()]$IntensiveUser, [Parameter()]$IntensivePass, [Parameter()][switch]$SkipCoreStatus, [Parameter()][switch]$ContinueOnPingError, [Parameter()]$ScriptTimeout, [Parameter()]$DownloadFile ) $FailedComputers = $Computers | Where-Object { $_.connection.ConnectionFailure -ne $true -AND $_.whamscript.scriptfailure -eq $true } If (($FailedComputers | Measure-Object).count -gt 0) { sleep 5 $ComputersRecieved = Run-WHAMScript -computers $FailedComputers -joblimit $joblimit -intensiveuser $IntensiveUser -IntensivePass $IntensivePass -ScriptTimeout $ScriptTimeout -DownloadFile $DownloadFile $comps = Set-WHAMMergeScriptComputers -ComputersRecieved $ComputersRecieved -computers $computers $computers = $comps } else { write-host "no failed script jobs" } return $computers } Function Update-WHAMExecutionLog { Param ( [Parameter(mandatory=$true)]$Computers, [Parameter(mandatory=$true)]$TotalConnectionJobTime, [Parameter(mandatory=$true)]$TotalScriptJobTime, [Parameter(mandatory=$true)]$version, [Parameter(mandatory=$true)]$FunctionNames ) $LogPath = "D:\DFS-Public\WHAM\Dev\Log" #if we dont have anything in fuction names, it means someone used a scriptblock into the wham controller and didnt use the template module IF ($FunctionNames.Length -lt 1) { $FunctionNames = "Custom script" } #Set our group name if WHAM is being used for Imp QCs If ($FunctionNames -eq "_Group-QC") { $Scriptname = "WHAM_IMP" #import the list of devices we have already run wham on for imp qcs $ImpDevices = Import-Clixml "$LogPath\WHAM_IMP_Device_list.xml" } else { $Scriptname = "WHAM" } $AddToImpArray = @() ForEach ($Computer in $Computers) { $Success = $null $COnnection = $null $Script = $null $Logtoappstats = $null If (($Computer.Connection.ConnectionFailure -ne $true) -and ($COmputer.WHAMScript.ScriptFailure -ne $true)) { $Success = $true #check if we are running an Imp QC If ($FunctionNames -eq "_Group-QC") { #If we are, check if its already been logged to app stats If ($ImpDevices -contains $Computer.ServerName){ $Logtoappstats = "NoLog" }else{ $AddToImpArray += $Computer.ServerName } } If ($Logtoappstats -ne "NoLog"){ $Params = @{ appid = $Scriptname version = $version device = $Computer.ServerName Account = $computer.Account status = "SUCCESS" UseCurrentDate = $true notes = $FunctionNames } $APIResponse = Update-AppStatAPI @Params If(-NOT $APIResponse){ New-AppStatsFile @Params } } } IF ($COmputer.Connection.ConnectionFailure){ $COnnection = $False }ELSE{ $COnnection = $True } IF ($COmputer.WHAMScript.ScriptFailure){ $Script = $False }ELSE{ $Script = $True } #Create the info used in our CSV log $temp = New-Object PSObject -Property @{ Date = Get-Date -format d Time = Get-Date -format t Scriptname = $Scriptname ServerName = $Computer.Servername TemplatesSelected = $FunctionNames Success = $Success Connection = $COnnection ConnectionFailureReason = $COmputer.Connection.ConnectionFailureReason Script = $Script ScriptFailureReason = $COmputer.WHAMScript.ScriptFailureReason UserName = [Environment]::UserName TotalConnectionJobTime = $ConnectionJobTime TotalScriptJobTime = $TotalScriptJobTime } #Write to our CSV log $temp | Select-Object Date,Time,Scriptname,ServerName,UserName,Success,TemplatesSelected,Connection,Script,ConnectionFailureReason,ScriptFailureReason,TotalConnectionJobTime,TotalScriptJobTime | Export-Csv -Path "$LogPath\WHAM_Execution_Log.csv" -Append -NoTypeInformation } If ($FunctionNames -eq "_Group-QC"){ $NewImpDevices = $ImpDevices + $AddToImpArray #export the list of devices we have already run wham on for imp qcs $NewImpDevices | Export-Clixml "D:\DFS-Public\WHAM\Log\WHAM_IMP_Device_list.xml" -Force } } Function Remove-Passwords { Param( [Parameter()]$Computer ) $ExportComputer = $computer IF ($ExportComputer.RackPass) { $ExportComputer.RackPass = $null } IF ($ExportComputer.AdminPass) { $ExportComputer.AdminPass = $null } IF ($ExportComputer.DomainUser) { $ExportComputer.DomainUser = $null } IF ($ExportComputer.DomainPass) { $ExportComputer.DomainPass = $null } IF ($ExportComputer.IntensivePass) { $ExportComputer.IntensivePass = $null } IF ($ExportComputer.IntensiveUser) { $ExportComputer.IntensiveUser = $null } IF ($ExportComputer.Connection) { $ExportComputer.Connection = $null } Return $ExportComputer } Function Display-ConnectionTestSummary{ Param ( [Parameter(Mandatory=$true)]$Computers ) write-host "Connection test summary:" -ForegroundColor Yellow write-host " " $Display = @() ForEach ($Computer in $Computers){ $temp = New-Object PSObject -Property @{ Servername = $Computer.Servername ConnectionFailure = $Computer.Connection.ConnectionFailure ConnectionFailureReason = $Computer.Connection.ConnectionFailureReason ConnectionType = $Computer.Connection.ConnectionType ValidCredentials = $Computer.Connection.ValidCredentials PSEXECErrorReason = $Computer.Connection.PSEXECErrorReason WinRMErrorReason = $Computer.Connection.WinRMErrorReason } $Display += $temp } $Display = $Display | Sort-Object connectionfailure -Descending ForEach ($computer in $Display){ #Pad string is in WHAM_data_execution.psm1 [string]$Servername = Pad-String -String $($Computer.Servername) -StringTotalLength 16 #write result to screen if ($Computer.ConnectionFailure){ [string]$FailureReason = $Computer.PSEXECErrorReason[0] Write-Host "Failure: " -NoNewline -ForegroundColor Red Write-Host "$Servername`: $FailureReason" }else{ if ($($COmputer.ConnectionType) -like "WINRM"){ [string]$temp = $($COmputer.ConnectionType) $ConnectionType = Pad-String -String $temp -StringTotalLength 6 }else{ $ConnectionType = $($COmputer.ConnectionType) } [string]$temp2 = $($COmputer.ValidCredentials) $ValidCredentials = Pad-String -String $temp2 -StringTotalLength 10 write-Host "Success: " -NoNewline -ForegroundColor Green Write-Host "$Servername`: $ConnectionType with: $ValidCredentials" } } } Function Pad-String{ Param( [Parameter(mandatory=$true)]$StringTotalLength, [Parameter(mandatory=$true)][string]$String ) if (($String | Measure-Object -Character).Characters -ge $StringTotalLength){ $String = $String.substring(0, $StringTotalLength) }else{ $Pad = $StringTotalLength - ($String | Measure-Object -Character).Characters $arr = 0 do{ [string]$String = "$String " $arr++ } until ($arr -eq $Pad) } Return $String }