DSCResources/DSC_ExchJetstress/DSC_ExchJetstress.psm1

function Get-TargetResource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Performance', 'Stress', 'DatabaseBackup', 'SoftRecovery')]
        [System.String]
        $Type,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressPath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressParams,

        [Parameter()]
        [System.UInt32]
        $MaxWaitMinutes = 0,

        [Parameter()]
        [System.UInt32]
        $MinAchievedIOPS = 0
    )

    Write-FunctionEntry -Parameters @{
        'JetstressPath'   = $JetstressPath
        'JetstressParams' = $JetstressParams
    } -Verbose:$VerbosePreference

    $returnValue = @{
        Type            = [System.String] $Type
        JetstressPath   = [System.String] $JetstressPath
        JetstressParams = [System.String] $JetstressParams
        MaxWaitMinutes  = [System.UInt32] $MaxWaitMinutes
    }

    $returnValue
}

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Performance', 'Stress', 'DatabaseBackup', 'SoftRecovery')]
        [System.String]
        $Type,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressPath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressParams,

        [Parameter()]
        [System.UInt32]
        $MaxWaitMinutes = 0,

        [Parameter()]
        [System.UInt32]
        $MinAchievedIOPS = 0
    )

    Write-FunctionEntry -Parameters @{
        'JetstressPath'   = $JetstressPath
        'JetstressParams' = $JetstressParams
    } -Verbose:$VerbosePreference

    $jetstressRunning = Test-JetstressRunning
    $jetstressSuccessful = Test-JetstressSuccessful @PSBoundParameters

    if ($jetstressSuccessful -eq $false -and (Get-ChildItem -LiteralPath "$($JetstressPath)" | Where-Object { $null -ne $_.Name -like "$($Type)*.html" }))
    {
        throw "Jetstress was previously executed and resulted in a failed run. Clean up any $($Type)*.html files in the Jetstress install directory before trying to run this resource again."
    }

    if ($jetstressRunning -eq $false) # Jetstress isn't running. Kick it off
    {
        $initializingESE = $false

        # If the ESE counters haven't been registered for Perfmon, Jetstress is going to need to initialize ESE and restart the process.
        if ((Test-Path -LiteralPath "$($env:SystemRoot)\Inf\ESE\eseperf.ini") -eq $false)
        {
            $initializingESE = $true
        }

        # Create and start the Jetstress scheduled task
        Start-JetstressOperation @PSBoundParameters

        # Give an additional 60 seconds if ESE counters were just initialized.
        if ($initializingESE -eq $true)
        {
            Write-Verbose -Message 'Jetstress has never initialized performance counters for ESE. Waiting a full 60 seconds for this to occurr'

            Start-Sleep -Seconds 5

            for ($i = 55; $i -gt 0; $i--)
            {
                $jetstressRunning = Test-JetstressRunning

                if ($jetstressRunning -eq $false)
                {
                    break
                }
                else
                {
                    Start-Sleep -Seconds 1
                }
            }

            <#
                I've found that Jetstress doesn't always restart after loading ESE when running as local system in a scheduled task in the background
                If Jetstress isn't running at this point, but the perf counters were registered, we probably need to reboot the server
                If Jetstress isn't running and ESE is not registered, something failed.
            #>

            if ($jetstressRunning -eq $false)
            {
                if ((Test-Path -LiteralPath "$($env:SystemRoot)\Inf\ESE\eseperf.ini") -eq $true)
                {
                    Write-Verbose -Message 'ESE performance counters were registered. Need to reboot server.'

                    Set-DSCMachineStatus -NewDSCMachineStatus 1
                    return
                }
                else
                {
                    throw 'Jetstress failed to register MSExchange Database performance counters'
                }
            }
            else
            {
                # Looks like Jetstress restarted itself successfully. Let's let it run.
            }
        }
        else
        {
            $jetstressRunning = Test-JetstressRunning
        }

        # Wait up to a minute for Jetstress to start. If it hasn't started by then, something went wrong
        $checkMaxTime = [DateTime]::Now.AddMinutes(1)

        while ($jetstressRunning -eq $false -and $checkMaxTime -gt [DateTime]::Now)
        {
            $jetstressRunning = Test-JetstressRunning

            if ($jetstressRunning -eq $false)
            {
                Start-Sleep -Seconds 1
            }
        }

        if ($jetstressRunning -eq $false)
        {
            throw 'Waited 60 seconds after launching the Jetstress scheduled task, but failed to detect that JetstressCmd.exe is running'
        }
    }

    while ($jetstressRunning -eq $true)
    {
        Write-Verbose -Message "Jetstress is still running at '$([DateTime]::Now)'."

        # Wait for 5 minutes before logging to the screen again, but actually check every 5 seconds whether Jetstress has completed.
        for ($i = 0; $i -lt 300 -and $jetstressRunning -eq $true; $i += 5)
        {
            $jetstressRunning = Test-JetstressRunning

            if ($jetstressRunning -eq $true)
            {
                Start-Sleep -Seconds 5
            }
        }
    }

    # Check the final status on the Jetstress run
    if ($jetstressRunning -eq $false)
    {
        Write-Verbose -Message "Jetstress testing finished at '$([DateTime]::Now)'."

        $overallTestSuccessful = Test-JetstressSuccessful @PSBoundParameters

        if ($overallTestSuccessful -eq $false)
        {
            throw 'Jetstress finished running, but the test did not complete successfully'
        }
        else
        {
            Write-Verbose -Message 'Jetstress finished, and the configured test passed'
        }
    }
    else
    {
        throw "Jetstress is still running after waiting $($MaxWaitMinutes) minutes"
    }
}

function Test-TargetResource
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Performance', 'Stress', 'DatabaseBackup', 'SoftRecovery')]
        [System.String]
        $Type,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressPath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressParams,

        [Parameter()]
        [System.UInt32]
        $MaxWaitMinutes = 0,

        [Parameter()]
        [System.UInt32]
        $MinAchievedIOPS = 0
    )

    Write-FunctionEntry -Parameters @{
        'JetstressPath'   = $JetstressPath
        'JetstressParams' = $JetstressParams
    } -Verbose:$VerbosePreference

    $jetstressRunning = Test-JetstressRunning -MaximumWaitSeconds 1

    if ($jetstressRunning -eq $true)
    {
        return $false
    }
    else
    {
        $jetstressSuccessful = Test-JetstressSuccessful @PSBoundParameters

        return $jetstressSuccessful
    }
}

# Checks whether the JetstressCmd.exe process is currently running
function Test-JetstressRunning
{
    $process = Get-Process -Name JetstressCmd -ErrorAction SilentlyContinue

    return ($null -ne $process)
}

# Used to create a scheduled task which will initiate the Jetstress run
function Start-JetstressOperation
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Performance', 'Stress', 'DatabaseBackup', 'SoftRecovery')]
        [System.String]
        $Type,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressPath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressParams,

        [Parameter()]
        [System.UInt32]
        $MaxWaitMinutes = 0,

        [Parameter()]
        [System.UInt32]
        $MinAchievedIOPS = 0
    )

    $fullPath = Join-Path -Path "$($JetstressPath)" -ChildPath "JetstressCmd.exe"

    Start-ExchangeScheduledTask -Path "$fullPath" -Arguments "$JetstressParams" -WorkingDirectory "$JetstressPath" -TaskName 'Jetstress' -MaxWaitMinutes $MaxWaitMinutes -Verbose:$VerbosePreference -TaskPriority 1
}

# Looks in the latest Type*.html file to determine whether the last Jetstress run passed
function Test-JetstressSuccessful
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Performance', 'Stress', 'DatabaseBackup', 'SoftRecovery')]
        [System.String]
        $Type,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressPath,

        [Parameter(Mandatory = $true)]
        [System.String]
        $JetstressParams,

        [Parameter()]
        [System.UInt32]
        $MaxWaitMinutes = 0,

        [Parameter()]
        [System.UInt32]
        $MinAchievedIOPS = 0
    )

    $overallTestSuccessful = $false
    $achievedIOPSTarget = $false

    $outputFiles = Get-ChildItem -LiteralPath "$($JetstressPath)" | Where-Object { $_.Name -like "$($Type)*.html" }

    if ($null -ne $outputFiles -and $outputFiles.Count -ge 1)
    {
        $outputFiles = $outputFiles | Sort-Object -Property LastWriteTime -Descending

        $latest = $outputFiles[0]

        $content = Get-Content -LiteralPath "$($latest.FullName)"

        $foundOverallResults = $false
        $foundAchievedIOPS = $false

        for ($i = 0; $i -lt $content.Length -and ($foundOverallResults -eq $false -or $foundAchievedIOPS -eq $false); $i++)
        {
            if ($content[$i].Contains("<td class=`"grid_row_header`">Overall Test Result</td>") -or $content[$i].Contains("<td class=`"grid_row_header`">Achieved Transactional I/O per Second</td>"))
            {
                $resultStart = $content[$i + 1].IndexOf('>') + 1
                $resultEnd = $content[$i + 1].LastIndexOf('<')

                $result = $content[$i + 1].Substring($resultStart, $resultEnd - $resultStart)

                if ($content[$i].Contains("<td class=`"grid_row_header`">Overall Test Result</td>"))
                {
                    $foundOverallResults = $true

                    Write-Verbose -Message "File '$($latest.FullName)' has an 'Overall Test Result' of '$result'"

                    if ($result -like 'Pass')
                    {
                        $overallTestSuccessful = $true
                    }
                }
                else
                {
                    $foundAchievedIOPS = $true

                    if ([System.String]::IsNullOrEmpty($result) -eq $false)
                    {
                        Write-Verbose -Message "File '$($latest.FullName)' has an 'Achieved Transactional I/O per Second' value of '$result'"

                        [Decimal] $decResult = [Decimal]::Parse($result)

                        if ($decResult -ge $MinAchievedIOPS)
                        {
                            $achievedIOPSTarget = $true
                        }
                    }
                    else
                    {
                        Write-Verbose -Message "Value for 'Achieved Transactional I/O per Second' is empty"
                    }
                }
            }
        }

        if ($foundOverallResults -eq $false)
        {
            Write-Verbose -Message "Unable to find 'Overall Test Result' in file '$($latest.FullName)'"
        }

        if ($foundAchievedIOPS -eq $false)
        {
            Write-Verbose -Message "Unable to find 'Achieved Transactional I/O per Second' in file '$($latest.FullName)'"
        }
    }
    else
    {
        Write-Verbose -Message "Unable to find any files matching '$($Type)*.html' in folder '$($JetstressPath)'"
    }

    return ($overallTestSuccessful -eq $true -and $achievedIOPSTarget -eq $true)
}

Export-ModuleMember -Function *-TargetResource