﻿
# **********************************************************
#                     Script parameters
# **********************************************************
Param( 
    [parameter(Mandatory=$true)][int]$Iterations,
    [parameter(Mandatory=$false)][string]$FileSetPath,
    [parameter(Mandatory=$true)][int32]$AnalysisOption,
    [parameter(Mandatory=$false)][switch]$DoReboots
)

# **********************************************************
#                     Definitions
# **********************************************************


# Set to true if you need to hang the script at the end to check console output
$debugFlag = $false

[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")

# Need to know where our script is so we can use full path to all our dependent files.
$InvocationPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
$ToolsPath = $InvocationPath
$ResourcesPath = Join-Path -Path $InvocationPath resources
$ScriptsPath = Join-Path -path $InvocationPath scripts
$FileSetPath = Join-Path -path $InvocationPath content
$ConfigFile = "$FileSetPath\office_config.csv"


# load shared definitions
Import-Module $ScriptsPath\AxePSHelper.psm1 -WarningAction SilentlyContinue
Import-Module $ScriptsPath\OfficePerfPSModule.psm1 -WarningAction SilentlyContinue

# Proc architecture
$arch = Get-ProcessorArchitecture

# Tools and resources
$archtools = Join-Path -path $ToolsPath $arch
$WPTPath = $archtools
$env:path = "$WPTPath;" + $env:path

Set-Variable REBOOT_DELAY_SECONDS -option Constant -value 180
Set-Variable WPRP_FILE -option Constant -value 'office.wprp'
Set-Variable PROFILE_FILE -option Constant -value 'office.wpaProfile'

Add-Type @"

    using System;

    public class Timing
    {
        public int iteration = 0;
        public string testcase = "";
        public int timingms = 0;
    }
"@


Add-Type @"

  using System;
  using System.Runtime.InteropServices;

  public class WindowHandler 
  {
     [DllImport("user32.dll")]
     [return: MarshalAs(UnmanagedType.Bool)]
     public static extern bool SetForegroundWindow(IntPtr hWnd);
  }
"@


#framework files
$XMLResultsFile = "$AXEResultsPath\results.xml"
$RebootTempFile = "$AXETempPath\reboot.txt"



# This try/finally block wraps the whole script so we can always properly dispose of objects.
#
try {

    function AnalyzeTraceFile {
    param (
        [parameter(Mandatory=$true)][string]$ETLFile,
        [parameter(Mandatory=$true)][string]$TestCase,
        [parameter(Mandatory=$true)][int32]$Iteration
    )

        "Process ETL $ETLFile ..." | Out-AxeInfo

        $coll = @()

        try {
            Set-Location -Path $WPTPath
            Invoke-Expression "wpaexporter.exe -i '$ETLFile' -profile '$ResourcesPath\$PROFILE_FILE' -prefix $TestCase -outputfolder '$AXEResultsPath' > $AXEResultsPath\exporter.log" -ErrorAction SilentlyContinue

            $res = import-csv -Path "$AXEResultsPath\$($TestCase)Regions_of_Interest_BiTRegions.csv" -Header Region, Op, Instance, Duration | select -skip 1

            $m = $res | where {$_.Region -match "Automation/.+"}

            foreach ($i in $m)
            {
                $delta = [int](1000*($i.Duration))
                $scenario = ($i.Region).Split("/")[1]
                "$scenario Delta: $delta ms" | Out-AxeInfo

                $Timing = New-Object Timing
                $Timing.testcase = "$($TestCase)_$($scenario)"
                $Timing.iteration = $Iteration
                $Timing.timingms = $delta
                $coll += $Timing
            }
        }
        catch {
            "Unable to process ETL trace" | Out-AxeInfo
            return 0
        }

        return $coll
    }

    function WaitUIReady {
    param (
        [parameter(Mandatory=$true)][string]$Process,
        [parameter(Mandatory=$true)][string]$WindowTitle
    )

        "Trying to find process/window combination ($Process / $WindowTitle)..." | Out-AxeInfo
        Start-Sleep -Milliseconds 100
        while (-not ((Get-Process -Name $Process).MainWindowTitle -match "$WindowTitle")){
            Start-Sleep -Milliseconds 50
        }

        "Found process/window combination ($Process / $WindowTitle)" | Out-AxeInfo

    }


    function TerminateProcessGracefully {
    param (
        [parameter(Mandatory=$true)][string]$Process,
        [parameter(Mandatory=$true)][string]$WindowTitle
    )

        
        Start-Sleep -Seconds 5

        "Bring window to foreground ($Process/$WindowTitle)..." | Out-AxeInfo

        try {

            [void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")

            $proc = (Get-Process -Name $Process) | where {$_.MainWindowTitle -match "$WindowTitle"}

            if ($proc) {

                if (($proc.MainWindowHandle -ne $null) -and ($proc.MainWindowHandle -ne 0)) {
                    [void] [WindowHandler]::SetForegroundWindow($proc.MainWindowHandle)
                }
                else {
                    "Window handle is null. Skipped set focus command." | Out-AxeInfo
                }

                Start-Sleep -Seconds 2

                "Send Alt-F4 to window and wait 5 seconds for graceful exit..." | Out-AxeInfo

                [System.Windows.Forms.SendKeys]::SendWait("%{F4}")
                Start-Sleep -Seconds 5
            }
            else {
                "Cannot find process/window combination..." | Out-AxeInfo
                "Assuming app has focus and sending Alt-F4 anyways..." | Out-AxeInfo

                [System.Windows.Forms.SendKeys]::SendWait("%{F4}")
                Start-Sleep -Seconds 5
            }

        }
        catch {}

        Get-ScreenCapture ((Get-Item env:\AssessmentResultsPath).value)

        if (Get-Process -Name $Process -ErrorAction SilentlyContinue) { 
            "Process was not terminated by Alt+F4 keystroke. Killing process." | Out-AxeInfo
            Stop-Process -Name $Process -ErrorAction SilentlyContinue
            Start-Sleep -Seconds 5
        } 
        else { 
            "Process was correctly terminated by Alt+F4 keystroke" | Out-AxeInfo
        }

    }


    function PerformAppLaunch {
    param (
        [parameter(Mandatory=$true)][boolean]$trace,
        [parameter(Mandatory=$false)][boolean]$analysis,
        [parameter(Mandatory=$true)][System.Object]$file,
        [parameter(Mandatory=$false)][int32]$iteration
    )

        $p = $FileSetPath + "\" + $file.file
        $p | Out-AxeInfo

        try {
            (ls $p).LastWriteTime = Get-Date 
        }
        catch {
            "Unable to update timestamp" | Out-AxeInfo
        }


        if ($trace) {

            if ($analysis) {
                wpr -start "$ResourcesPath\$WPRP_FILE!WD.verbose" -filemode
            } 
            else {
                if ($file.custom_wprp) {
                    wpr -start "$FileSetPath\$($file.custom_wprp)" -filemode
                }
                else {
                    wpr -start "$ResourcesPath\$WPRP_FILE!WD.light"
                }
            }

            Start-Sleep -Seconds 10
        }

        Trace-AxeOperationStart -OpCode 200 -Payload $file.name

        if (Test-Path -Path $p) {
            $proc = Start-Process -FilePath "$p" -PassThru
        }
        else {
                "File not found!" | Out-AxeInfo
        }

        WaitUIReady -Process $file.procName -WindowTitle $file.wndName

        Trace-AxeOperationStop -OpCode 200 -Payload $file.name

        if ($file.custom_keysend) {
            Start-Sleep -Seconds 10
            [System.Windows.Forms.SendKeys]::SendWait($file.custom_keysend)
            Start-Sleep -Seconds 10
            [System.Windows.Forms.SendKeys]::SendWait("{ESC}")
        }
            
        if ($trace) { 

            "Wait 5 seconds" | Out-AxeInfo
            Start-Sleep -Seconds 5

            if ($analysis) {
                $ETLFile = "$AXEResultsPath\Analysis_$($file.name).etl"
            }
            else {
                $ETLFile = "$AXEResultsPath\Timing_$($file.name)_$iteration.etl"
            }

            "Save ETL" | Out-AxeInfo
            wpr -stop "$ETLFile" "$($file.name)"
        }


        TerminateProcessGracefully -Process $file.procName -WindowTitle $file.wndName

    }


    #*****************************************************************
    #
    #                PREP LOGIC
    #
    #*****************************************************************

    # parameters processing

    if (-not (Test-Path $FileSetPath)) {
         throw [System.IO.FileNotFoundException] "$FileSetPath not found."
    }
    
    if (-not (Test-Path $ConfigFile)) {
        throw [System.IO.FileNotFoundException] "$ConfigFile not found."
    } else {
        $FilesList = Import-Csv -Path "$ConfigFile"
    }

    if (($Iterations -lt 1) -or ($Iterations -gt 10)) {
        throw "Invalid value (@$Iterations) for parameter 'Iterations'. Range: [1..10]"
    }


    # if AnalysisOnly is selected, then then iterations parameter is set to 0 (refers to Timing runs)
    if ($AnalysisOption -eq 3) {
        $Iterations = 0
    }


    # iteration 0 is the "training" run. we skip it if EmptyStandbyList is selected
    $FirstIteration = 0



    #*****************************************************************
    #
    #                TEST LOGIC
    #
    #*****************************************************************

    if ($DoReboots) {

        $RebootCount = $env:AssessmentRebootIteration
        "Reboot count: $RebootCount" | Out-AxeInfo

        #
        # We haven't rebooted yet, so init the status file and reboot
        #

        if ($RebootCount -eq 0) {

            "1!0" > $RebootTempFile
            Start-AxeReboot
            shutdown /r /t 5
            Exit 0
        }


        #
        # Get current test status
        #

        if (-not (Test-Path $RebootTempFile)) { throw [System.IO.FileNotFoundException] "$RebootTempFile not found." }
        $Status = Get-Content $RebootTempFile
        $CurrentIterationIndex = [int32]($Status.Split('!')[0])
        $CurrentFileIndex = [int32]($Status.Split('!')[1])

        "Iteration: $CurrentIterationIndex on $($Iterations)" | Out-AxeInfo
        "App: $($CurrentFileIndex + 1) on $(([array]$FilesList).count)" | Out-AxeInfo


        #
        # Determine max indexes and set analysis trace flag
        #

        $MaxFileListIndex = ([array]$FilesList).count - 1 

        if (($AnalysisOption -eq 3) -or ($AnalysisOption -eq 1)) { #analysisonly or timinganalysis options
            $MaxIterationIndex = $Iterations + 1

            if ($MaxIterationIndex  -eq $CurrentIterationIndex) {
                $AnalysisFlag = $true
            } else {
                $AnalysisFlag = $false
            }
        }
        else {
            $MaxIterationIndex = $Iterations 
            $AnalysisFlag = $false
        }


        #
        # Sleep to let system stabilize and then launch app with specified file
        #

        "Sleep for $REBOOT_DELAY_SECONDS seconds..." | Out-AxeInfo
        Start-Sleep -Seconds $REBOOT_DELAY_SECONDS
        
        $file = ([array]$FilesList)[$CurrentFileIndex]
        PerformAppLaunch -trace $true -analysis $AnalysisFlag -file $file -iteration $CurrentIterationIndex
                

        #
        # Check stop condition, increment indexes, update status file and reboot as necessary
        #

        if (($CurrentIterationIndex -eq $MaxIterationIndex) -and ($CurrentFileIndex -eq $MaxFileListIndex)) 
        {
            "Test Pass completed !" | Out-AxeInfo
        }
        elseif ($CurrentFileIndex -eq $MaxFileListIndex) 
        {
            "$($CurrentIterationIndex + 1)!0" > $RebootTempFile

            Start-AxeReboot
            shutdown /r /t 5
            Exit 0
        }
        else 
        {
            "$CurrentIterationIndex!$($CurrentFileIndex + 1)" > $RebootTempFile

            Start-AxeReboot
            shutdown /r /t 5
            Exit 0
        }

    }
    else 
    {
        "Number of files to open: $(([array]$FilesList).count)" | Out-AxeInfo
        "Number of iterations to perform: $($Iterations)" | Out-AxeInfo

        for ($i=$FirstIteration; $i -le ($Iterations + 1); $i++)  {

            $FileCount = 0
            "Iteration $i" | Out-AxeInfo


            foreach ($file in $FilesList) {

                $FileCount += 1 
                 "File #$FileCount : $($file.name)" | Out-AxeInfo


                 if ($i -eq 0) { # training

                    PerformAppLaunch -trace $false -file $file

                 }
                 else {

                    if ($i -eq ($Iterations + 1)) { # analysis
        
                    
                        if ($AnalysisOption -eq 2) { # skip analysis if not selected
                            continue
                        }

                        PerformAppLaunch -trace $true -analysis $true -file $file
            
                    }
                    else { # timing
                
                        PerformAppLaunch -trace $true -analysis $false -file $file -iteration $i
                
                    }

                 }
 
            }

        }

    }

    #*****************************************************************
    #
    #                POST PROCESSING
    #
    #*****************************************************************

    $DataCollection = @()

    $ETLFiles = Get-ChildItem -Path "$AXEResultsPath" -Filter "Timing*.etl"

    $r = [regex] "Timing_(.+)_(.+)\.etl"

    $TraceCollection = @()

    foreach ($ETLFile in $ETLFiles) {

        if ($ETLFile.Name -match $r) {

            $i = $matches[2]
            $testcase = $matches[1]

            $o = AnalyzeTraceFile -ETLFile $ETLFile.FullName -TestCase $testcase -Iteration $i
            
            $TraceCollection += $ETLFile.Name

            $DataCollection += $o
        }
    }


    $DataCollection | Export-Csv "$AXEResultsPath\results.csv" -NoTypeInformation

    # Create the XML helper object
    #
    $XMLHelper = Get-AxeResultSnippet

    for ($i=1; $i -le $Iterations; $i++)  {

        $XMLIteration = $XMLHelper.AddIteration()

        $dataIteration = $DataCollection | where {$_.iteration -eq $i}

        $a = [int](Get-Average ($dataIteration | Foreach {$_.timingms}))

        $XMLIteration.AddMetricValue("AppAverage", $a)


        foreach ($data in $dataIteration)
        {
            $XMLTestCase = $XMLIteration.AddTestCase()
            $XMLTestCase.Key = "$($data.testcase)_duration"
            $XMLTestCase.Name = "$($data.testcase)"
            $XMLTestCase.AddMetricValue("Duration_ms", $data.timingms)
        }
    }

    $XMLHelper.Save($XMLResultsFile)
    

    #*****************************************************************
    #
    #                Cleanup
    #
    #*****************************************************************

}
catch
{
    $_ | Out-AxeError $_.Exception.Hresult
    $message = $_.Exception.Message + [Environment]::NewLine + $_.InvocationInfo.PositionMessage
    $message | Out-AxeInfo
    throw
}
finally {


    # Cancel any ETL tracing that might be going on
    #
    if (-not ((wpr -status) -match "not recording")) {
        wpr -cancel
    }

    # We should always properly dispose of this guy.
    #
    Start-AxeCleanup

    # Hold off before exiting if DEBUG==TRUE
    #
    if ($debugFlag){
        Read-Host "Press ENTER key to exit"
    }
}