Functions/Get-OSPerfTuningStatus.ps1

function Get-OSPerfTuningStatus
{
    <#
    .SYNOPSIS
    Check the status of each of the performance tuning items for the OutSystems platform server.
 
    .DESCRIPTION
    Gives a detailed description of the status of item of the performance tuning section for the OutSystems platform server.
 
    .EXAMPLE
    Get-OSPerfTuningStatus
    #>


    [CmdletBinding()]
    param(
    )

    begin
    {
        LogMessage -Function $($MyInvocation.Mycommand) -Phase 0 -Stream 0 -Message "Starting"
        SendFunctionStartEvent -InvocationInfo $MyInvocation

        Function GetCompareText {
            param(
                [bool]$IsGreaterOrEqual
            )

            if ($IsGreaterOrEqual) 
            {
                $CompText = "greater or equal"
            }
            else
            {
                $CompText = "lesser"
            }

            return $CompText
        }

        Function GetIsOrIsntText {
            param(
                [bool]$Is
            )

            if ($Is) 
            {
                $CompText = "is"
            }
            else
            {
                $CompText = "is not"
            }

            return $CompText
        }

        Function CreatePerfTuningStatus {
            param(
                [Parameter(Mandatory = $true)]
                [String]$Title,

                [Parameter(Mandatory = $true)]
                [ScriptBlock]$ScriptBlock
            )

            $PerfTuningStatus = & $ScriptBlock

            $PerfTuningStatus.Title = $Title

            $TextStatus = "WILL"
            if (-not $PerfTuningStatus.ShouldBeSkipped)
            {
                $TextStatus = "$TextStatus DO"
            } else 
            {
                $TextStatus = "$TextStatus SKIP"
            }

            LogMessage -Function $($MyInvocation.Mycommand) -Phase 1 -Stream 0 -Message "$($PerfTuningStatus.Title): [$TextStatus]"

            foreach ($Message in $PerfTuningStatus.Messages)
            {
                LogMessage -Function $($MyInvocation.Mycommand) -Phase 1 -Stream 0 -Message " > $Message"
            }

            return $PerfTuningStatus
        }

        Function CreateResult {
            param(
                [Parameter(Mandatory = $true)]
                [Bool]$ShouldBeSkipped,

                [Parameter(Mandatory = $true)]
                [AllowEmptyCollection()]
                [String[]]$Messages
            )

            $Result = @{}
            $Result.ShouldBeSkipped = $ShouldBeSkipped
            $Result.Messages = $Messages

            return $Result
        }

        $PerfTuning = @{}
        $PerfTuning.SomethingToDo = $False
        $PerfTuning.Sections = @{}
    }

    process
    {
        # Check if user is admin
        if (-not $(IsAdmin))
        {
            LogMessage -Function $($MyInvocation.Mycommand) -Phase 1 -Stream 3 -Message "The current user is not Administrator or not running this script in an elevated session"
            WriteNonTerminalError -Message "The current user is not Administrator or not running this script in an elevated session"

            $installResult.Success = $False
            $installResult.ExitCode = -1
            $installResult.Message = 'The current user is not Administrator or not running this script in an elevated session'

            return $installResult
        }
 
        $PerfTuning.Sections.ProcessSchedulingConfig = CreatePerfTuningStatus -Title "Setting Windows processor scheduling priority to 'background services'" `
            -ScriptBlock `
            {
                $ShouldBeSkipped = $True
                $Messages = @()

                try
                {
                    $Value = RegRead -Path "HKLM:\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\PriorityControl" -Name "Win32PrioritySeparation" -Type "Dword"

                    $ShouldBeSkipped = ($Value -eq 24)

                    if ($ShouldBeSkipped)
                    {
                        $Messages = @("Already set to 'background services'.")
                    }
                    else
                    {
                        $Messages = @("Not set to 'background services'.")
                    }
                }
                catch
                {
                    $Messages = @("Unable to determine current value. Skipping.")
                }

                # No current scenarios where we should skip this
                return $(CreateResult -ShouldBeSkipped $ShouldBeSkipped -Messages $Messages)
            }

        $PerfTuning.Sections.NETConfig = CreatePerfTuningStatus -Title ".NET upload size limits and execution timeout configuration" `
            -ScriptBlock `
            {
                $ShouldBeSkipped = $True
                $Messages = @()

                try
                {
                    $NETConfig = $(GetDotNetLimits)

                    $CurrentMaxRequestLength = $NETConfig.SystemWeb.HttpRuntime.maxRequestLength
                    $CurrentMaxRequestLengthInMB = "$($CurrentMaxRequestLength/1024) MB"
                    $CurrentExecutionTimeout = $NETConfig.SystemWeb.HttpRuntime.executionTimeout

                    # upload size limits default is 4096 KB
                    # https://docs.microsoft.com/en-us/dotnet/api/system.web.configuration.httpruntimesection.maxrequestlength
                    $DefaultMaxRequestLength = 4096
                    $DefaultMaxRequestLengthInMB = "$($DefaultMaxRequestLength/1024) MB"

                    # execution timeout default is 110 seconds
                    # https://docs.microsoft.com/en-us/dotnet/api/system.web.configuration.httpruntimesection.executiontimeout
                    [TimeSpan]$DefaultExecutionTimeout = "00:01:50"

                    $HasSmashableMaxRequestLength = $CurrentMaxRequestLength -eq $DefaultMaxRequestLength
                    $HasSmashableExecutionTimeout = $CurrentExecutionTimeout -eq $DefaultExecutionTimeout

                    $ShouldBeSkipped = $(-not $HasSmashableMaxRequestLength) -or $(-not $HasSmashableExecutionTimeout)

                    $CompTextMaxRequestLength = $(GetIsOrIsntText $HasSmashableMaxRequestLength)

                    $Messages += @("Current upload limit value ('$CurrentMaxRequestLengthInMB') $CompTextMaxRequestLength the default ('$DefaultMaxRequestLengthInMB').")

                    $CompTextExecutionTimeout = $(GetIsOrIsntText $HasSmashableExecutionTimeout)

                    $Messages += @("Current execution timeout value ('$CurrentExecutionTimeout') $CompTextExecutionTimeout the default ('$DefaultExecutionTimeout').")
                }
                catch
                {
                    $Messages = @("Unable to determine current values. Skipping.")
                }

                return $(CreateResult -ShouldBeSkipped $ShouldBeSkipped -Messages $Messages)
            }

        $PerfTuning.Sections.IISUploadSizeLimitsConfig = CreatePerfTuningStatus -Title "IIS upload size limits configuration" `
            -ScriptBlock `
            {
                $ShouldBeSkipped = $True
                $Messages = @()

                try
                {
                    $Filter = "system.webServer/security/requestFiltering/requestLimits"
                    $Name = "maxAllowedContentLength"

                    $MaxAllowedContentLength = $(GetWebConfigurationProperty -PSPath "MACHINE/WEBROOT/APPHOST" -Filter "$Filter" -Name "$Name")
                    # MaxAllowedContentLength default value is 30000000
                    # https://docs.microsoft.com/en-us/iis/configuration/system.webserver/security/requestfiltering/requestlimits/#configuration
                    $DefaultMaxAllowedContentLength = 30000000

                    $ShouldBeSkipped = $MaxAllowedContentLength -ne $DefaultMaxAllowedContentLength

                    $CompTextMaxAllowedContentLength = $(GetIsOrIsntText (-not $ShouldBeSkipped))

                    $Messages = @("Current max allowed content length value ('$($MaxAllowedContentLength.Value) Bytes') $CompTextMaxAllowedContentLength the default ('$DefaultMaxAllowedContentLength Bytes').")
                }
                catch
                {
                    $Messages = @("Unable to determine current value. Skipping.")
                }

                return $(CreateResult -ShouldBeSkipped $ShouldBeSkipped -Messages $Messages)
            }

        $PerfTuning.Sections.IISConnectionsConfig = CreatePerfTuningStatus -Title "IIS for unlimited connections configuration" `
            -ScriptBlock `
            {
                $ShouldBeSkipped = $True
                $Messages = @()

                try
                {
                    $IISConfigs = $(GetWebConfigurationProperty -PSPath "IIS:\" -Filter "system.applicationHost/sites/site[@name='Default Web Site']" -Name "Limits")

                    $CurrentValue = $IISConfigs.maxConnections
                    $RecommendedValue = $OSPerfTuningMaxConnections

                    $ShouldBeSkipped = $CurrentValue -ge $RecommendedValue

                    $CompText = $(GetCompareText -IsGreaterOrEqual $ShouldBeSkipped)

                    $Messages = @("Current value ('$CurrentValue') is $CompText than our recommended ('$RecommendedValue').")
                }
                catch
                {
                    $Messages = @("Unable to determine current value. Skipping.")
                }

                return $(CreateResult -ShouldBeSkipped $ShouldBeSkipped -Messages $Messages)
            }

        $PerfTuning.Sections.AppPoolsConfig = CreatePerfTuningStatus -Title "IIS Application Pools configuration" `
            -ScriptBlock `
            {
                $ShouldBeSkipped = $True
                $Messages = @()
                $AppPoolsToForciblyCreateAndConfig = @()

                try
                {
                    foreach ($AppPool in @("OutSystemsApplications", "ServiceCenterAppPool"))
                    {
                        if (-not $(Get-ChildItem -Path "IIS:\AppPools\$($AppPool)" -ErrorAction SilentlyContinue))
                        {
                            $AppPoolsToForciblyCreateAndConfig += $AppPool
                            $Messages += "'$AppPool' will be created and configured."
                        }
                        else
                        {
                            $Messages += "Detected that '$AppPool' may have user configurations. Skipping."
                        }
                    }

                    $ShouldBeSkipped = $($AppPoolsToForciblyCreateAndConfig.Count -eq 0)
                }
                catch
                {
                    $Messages += "Unable to determine application pools status. Skipping."
                }

                $Result = $(CreateResult -ShouldBeSkipped $ShouldBeSkipped -Messages $Messages)
                $Result.AppPoolsToForciblyCreateAndConfig = $AppPoolsToForciblyCreateAndConfig

                return $Result
            }

        foreach ($Status in $PerfTuning.Sections.values)
        {
            if (-not $Status.ShouldBeSkipped)
            {
                $PerfTuning.SomethingToDo = $True
                break
            }
        }

        return $PerfTuning
    }

    end
    {
        SendFunctionEndEvent -InvocationInfo $MyInvocation
        LogMessage -Function $($MyInvocation.Mycommand) -Phase 2 -Stream 0 -Message "Ending"
    }
}