Private/Test-Series.ps1
function Test-Series { <# .SYNOPSIS Checks the ports and services of a Windows Server. .DESCRIPTION Checks the ports and services of a Windows Server. This is mainly used in unit testing to validate the test suites before production. The basic flow is: Test Connectivity (ICMP) -> Test Ports & Services -> Test Additional Checks A failure at a previous state will assume failure further down and stop tests. We don't care if a SSL cert check is valid if one of the dependant ports is down. This is subject to change. Not all Test-Series will have additional checks, but this is a placeholder. .PARAMETER JSONFile Specify path to your config file to run checks against. This would be your EUCMonitoring.json, or your test configs. Specifying a JSONFile override any ConfigObject passed to it. .PARAMETER Series Specifies the name of the Series to run against. .NOTES Current Version: 1.0 Creation Date: 14/05/2018 .CHANGE CONTROL Name Version Date Change Detail Adam Yarborough 1.0 17/05/2018 Function Creation David Brett 1.1 16/06/2018 Updated Switch statement to splat the @params Added TestMode as a JSON Parameter to switch between basic and advanced tests Adam Yarborough 1.2 20/06/2018 Multi-result ChecksData support, cleanup Fixes #20 Fixes #24 Fixes #39 .EXAMPLE Test-Series -JSONFile "C:\Monitoring\EUCMonitoring.json" Worker #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline, Mandatory = $true)][string]$JSONFile, [Parameter(ValueFromPipeline, Mandatory = $true)][string]$Series ) Begin { } Process { Write-Verbose "Starting Test-Series on $Series." # Initialize Empty Results Write-Verbose "Initializing Results..." $Results = @() Write-Verbose "Loading config from $JSONFile" # Set up tests if ( test-path $JSONFile ) { $StartTime = (Get-Date) try { $ConfigObject = Get-Content -Raw -Path $JSONFile | ConvertFrom-Json -ErrorAction Stop } catch { throw "Error reading JSON. Please Check File and try again." } } else { Write-Verbose "Error opening JSON. Please Check File and try again." return $null } Write-Verbose "Loading the configuration for the $Series series" $Config = $ConfigObject.$Series if ( $null -eq $Config ) { Write-Verbose "Unable to find $Series series in $JSONFile. We shouldn't get here." return $null } $XdControllers = @() $RdsControllers = @() # Make sure we're allowed to test this series. if ( $false -eq $Config.Test) { return $null } # For the Series that have no servers configured, we will populate servers # with either global values, or amongst sets. I'd like to eventually be able # to have multiple sites definable. To do this, we let if ( $Series -eq "Worker" ) { Write-Verbose "Invoking Worker Targets" # If someone populates this, they know which servers to hit. if ( $null = $Config.Servers ) { $Config.Servers = @() } # Troll the Config object, get all the XdSite definitions from the workers # and pick either primary or secondary server as the controller that all the # checks will be done from. Add Controller to $Servers, so that for multiple # sites the checks will be done to all up servers. This will then funnel down # to the foreach loop. if ( $Config.XdSites ) { foreach ( $XdSite in $Config.XdSites ) { # Test Connection, add the first controller that responds to Servers # as well as XdControllers $Controller = "" if ( $null -eq $Config.Servers ) { $Config | Add-Member -NotePropertyName Servers -NotePropertyValue @() } if ( (Connect-Server $XdSite.PrimaryController ) -eq "Successful" ) { $Controller = $XdSite.PrimaryController $Config.Servers += $Controller $XdControllers += $Controller Write-Verbose "Adding XD Controller $Controller" } elseif ( (Connect-Server $XdSite.SecondaryController) -eq "Successful") { $Controller = $XdSite.SecondaryController $Config.Servers += $Controller $XdControllers += $Controller Write-Verbose "Adding XD Controller $Controller" } else { Write-Verbose "Could not connect to any controllers in $XDSite" $Errors += "Could not connect to any controllers in $XDSite." } } } # ! This is more of a placeholder than anything, as we haven't implemented # ! RDS Checks yet. if ( $Config.RdsSites ) { foreach ( $RdsSite in $Config.RdsSites ) { # Test Connection, add the first controller that responds to Servers # as well as XdControllers $Controller = "" if ( $null -eq $Config.Servers ) { $Config | Add-Member -NotePropertyName Servers -NotePropertyValue @() } if ( (Connect-Server $RdsSite.PrimaryController ) -eq "Successful" ) { $Controller = $RdsSite.PrimaryController $Config.Servers += $Controller $RdsControllers += $Controller Write-Verbose "Adding RDS Controller $Controller" } elseif ( (Connect-Server $RdsSite.SecondaryController) -eq "Successful") { $Controller = $RdsSite.SecondaryController $Config.Servers += $Controller $RdsControllers += $Controller Write-Verbose "Adding RDS Controller $Controller" } else { Write-Verbose "Could not connect to any controllers in $RDSSite" $Errors += "Could not connect to any controllers in $RDSSite." } } } } #END WORKER # This loops over each computer in $Config.Servers, checks the connection, # tests all ports defined in config, all services defined in the config, # and if none of those tests fail, it will do the additional checks. # Worker Series will populate with just the servers, but no ports or # Services, so they will be checks only. foreach ($ComputerName in $Config.Servers) { # State can be UP / DEGRADED / DOWN $State = "UP" $PortsUp = @() $PortsDown = @() $ServicesUp = @() $ServicesDown = @() $ChecksUp = @() $ChecksDown = @() $ChecksData = @() $Errors = @() if ((Connect-Server $ComputerName) -eq "Successful") { Write-Verbose "Series $Series - Connection Successful to $ComputerName" # Ports foreach ($Port in $Config.Ports) { Write-Verbose "Testing $ComputerName - Port $Port" if ( Test-NetConnection $ComputerName -Port $Port -InformationLevel Quiet ) { Write-Verbose "Success $ComputerName - Port $Port" $PortsUp += $Port } else { Write-Verbose "Failure $ComputerName - Port $Port" $State = "DEGRADED" $PortsDown += $Port $Errors += "$Port closed" } } # Windows Services foreach ($Service in $Config.Services) { $CurrentServiceStatus = Test-Service $ComputerName $Service If ($CurrentServiceStatus -eq "Running") { $ServicesUp += $Service } else { $State = "DEGRADED" $ServicesDown += $Service $Errors += "$Service not running" } } # Tests will return true or false, which will determine checkup or checkdown. # If it cannot invoke a check, it will create an error and be placed in checkdown. # Each check should be able to create their own influx data using the series # information, so update that function as well. if ( $State -eq "UP") { # ! There's probably a more elegant way of doing this. foreach ($CheckList in $Config.Checks) { foreach ( $Check in $CheckList.PSObject.Properties ) { $CheckName = $Check.Name $CheckValue = $Check.Value Write-Verbose "$Computername performing $CheckName" # IF the check cannot run the test successfully, it returns False. # If the check can run the test successfully, but there were problems # it will populate an Errors property in the returned object. $Values = @() if ($true -eq $CheckValue.test) { # ! Should we have a check enabled for each check? switch ($CheckName) { "XdDesktop" { if ( $ComputerName -in $XdControllers ) { $params = @{ Broker = $ComputerName; WorkerTestMode = $CheckValue.testmode; Workload = 'desktop'; BootThreshold = $CheckValue.BootThreshold; HighLoad = $CheckValue.HighLoad } $Values = Test-XdWorker @params } } "XdServer" { if ( $ComputerName -in $XdControllers ) { $params = @{ Broker = $ComputerName; WorkerTestMode = $CheckValue.testmode; Workload = 'server'; BootThreshold = $CheckValue.BootThreshold; HighLoad = $CheckValue.HighLoad } $Values = Test-XdWorker @params } } "XdSessionInfo" { if ( $ComputerName -in $XdControllers ) { $Values = Test-XdSessionInfo $ComputerName } } # License Checks "XdLicense" { $Values = Test-XdLicense $ComputerName $CheckValue.LicenseType } # Site/Env Checks "XdDeliveryGroupHealth" { if ( $ComputerName -in $XdControllers ) { $Values = Test-XdDeliveryGroupHealth $ComputerName } else { $Values = "SKIP CHECK" } } "XdCatalogHealth" { if ( $ComputerName -in $XdControllers ) { $Values = Test-XdCatalogHealth $ComputerName } else { $Values = "SKIP CHECK" } } "XdHypervisorHealth" { if ( $ComputerName -in $XdControllers ) { $Values = Test-XdHypervisorHealth $ComputerName } else { $Values = "SKIP CHECK" } } "XdControllerHealth" { if ( $ComputerName -in $XdControllers ) { $Values = Test-XdControllerHealth $ComputerName } else { $Values = "SKIP CHECK" } } # ! Placeholder "RdsDesktop" { if ( $ComputerName -in $RdsControllers ) { } } "RdsServer" { if ( $ComputerName -in $RdsControllers ) { } } "RdsSessionInfo" { if ( $ComputerName -in $RdsControllers ) { } } # XenServer # ! Not Tested "XenServer" { $XenServerPassword = ConvertTo-SecureString $CheckValue.Password -AsPlainText -Force $Values = Test-XenServer $ComputerName $CheckValue.Username $XenServerPassword } # Netscaler Checks # ! Changed to reflect JSON template "Netscaler" { $NetScalerUserName = $CheckValue.username $NetScalerPasswordPlain = $CheckValue.password $NetScalerPassword = ConvertTo-SecureString $NetScalerPasswordPlain -AsPlainText -Force $Values = Test-Netscaler $ComputerName $NetScalerUserName $NetScalerPassword } # ! Changed to reflect JSON template "NetscalerGateway" { $NetScalerUserName = $CheckValue.username $NetScalerPasswordPlain = $CheckValue.password $NetScalerPassword = ConvertTo-SecureString $NetScalerPasswordPlain -AsPlainText -Force $Values = Test-NetscalerGateway $ComputerName $NetScalerUserName $NetScalerPassword } # PVS # ! Not yet fully implemented. "PVSStats" { } # URL Checks "HTTPUrl" { $Url = "http://$($ComputerName):$($CheckValue.Port)$($CheckValue.Path)" Write-Verbose "Testing URL: $Url" $Values = Test-URL -Url $Url } "HTTPSUrl" { # Fixes #20 $Url = "https://$($ComputerName):$($CheckValue.Port)$($CheckValue.Path)" Write-Verbose "Testing URL: $Url" $Values = Test-URL -Url $Url } "ValidCert" { $Values = Test-ValidCert $ComputerName $CheckValue.Port } # Instead of a continue here, do nothing so that the test CheckName fails. Default { Write-Verbose "Could not find test function for $CheckName" } } } else { $Values = "SKIP CHECK" } # Validate Success # This is true for empty and $False values if ( $false -eq $Values ) { $ChecksDown += $CheckName $Errors += "$CheckName failed" $State = "DEGRADED" } # No need to have a check elseif ( "SKIP CHECK" -eq $Values ) { Write-Verbose "Skipping $CheckName" } # This might be redundant. else { # Gets here with a $True or an object returned. $ChecksUp += $CheckName # Just because we completed the test successfully, doesn't mean it was without # errors. if ("Boolean" -ne $Values.GetType().Name) { $Values | ForEach-Object { if ( $_.Errors.Count -gt 0 ) { Write-Verbose "Found Errors in $CheckName returned Values" $Errors += $_.Errors # $State = "DEGRADED" # ! Review } # Remove the check's errors since they've been passed to the Series. # Write-Verbose "Removing Errors from Values" # This works whether or not the property exists. $_.PSObject.Properties.Remove('Errors') # Now that we've removed Errors, if there's still data, lets pass it on. if ( $null -ne $_ ) { $ChecksData += [PSCustomObject]@{ CheckName = $CheckName Values = $_ } } } } } } } } # State is DEGRADED, we will not run additional checks. else { foreach ($Check in $Config.Checks) { $ChecksDown += $Check.PSObject.Properties.Name } } # Finalize State by making sure if no tests passed, it's the same as being down. If degraded, # There's no need to do further checks # ! Validate this is correct. if ( "DEGRADED" -eq $State ) { if ( (0 -eq $ServicesUp.Count) -and (0 -eq $PortsUp.Count) -and (0 -eq $ChecksUp.Count) ) { Write-Verbose "$ComputerName is effectively down." $Errors += "$ComputerName is effectively down." $State = "DOWN" } } } # Server is down - not responding to ping # ! Anything else to be set / returned? else { Write-Verbose "$ComputerName is down." $State = "DOWN" if ($null -ne $Config.Ports) { $PortsDown += $Config.Ports } if ($null -ne $Config.Services) { $ServicesDown += $Config.Services } foreach ($Check in $Config.Checks) { if ($null -ne $Check.PSObject.Properties.Name) { $ChecksDown += $Check.PSObject.Properties.Name } } $Errors += "$ComputerName is down." } Write-Verbose "ComputerName: $ComputerName" Write-Verbose "State: $State" Write-Verbose "Ports Up: $PortsUp" Write-Verbose "PortsDown: $PortsDown" Write-Verbose "ServicesUp: $ServicesUp" Write-Verbose "ServicesDown: $ServicesDown" Write-Verbose "ChecksUp: $ChecksUp" Write-Verbose "ChecksDown: $ChecksDown" Write-Verbose "CheckData: $CheckData" Write-Verbose "Errors: $Errors" # ! Did you alter results? Was there a good reason? $results += [PSCustomObject]@{ 'ComputerName' = $ComputerName 'State' = $State 'PortsUp' = $PortsUp 'PortsDown' = $PortsDown 'ServicesUp' = $ServicesUp 'ServicesDown' = $ServicesDown 'ChecksUp' = $ChecksUp 'ChecksDown' = $ChecksDown 'ChecksData' = $ChecksData 'Errors' = $Errors } } $EndTime = (Get-Date) Write-Verbose "Test-Series for $Series finished." Write-Verbose "Elapsed Time: $(($EndTime-$StartTime).TotalMinutes) Minutes" Write-Verbose "Elapsed Time: $(($EndTime-$StartTime).TotalSeconds) Seconds" # Write-Verbose "$(ConvertTo-Json -inputObject $Results)" return [PSCustomObject]@{ 'Series' = $Series 'Results' = $Results } } End { } } |