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