﻿
Param( 
    [parameter(Mandatory=$false)][string]$AxeCoreNet,
    [parameter(Mandatory=$false)][string]$ResultFile,
    [parameter(Mandatory=$false)][uint32]$Delay=0,
    [parameter(Mandatory=$false)][switch]$Restart,
    [parameter(Mandatory=$false)][int32]$RestartCount=-1
)

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


    # Need to know where our script is so we can use full path to all our dependent files.
    #
    $folderScript = Split-Path -Path $MyInvocation.MyCommand.Path -Parent

    # Try to load up Axe because it is cool.
    #
    $boolRunningUnderAxe = $false
    if ( $AxeCoreNet ) {
        $axeAssembly = [Reflection.Assembly]::LoadFrom( $AxeCoreNet )
        if ( $axeAssembly ) {
            $axeSupport = [Microsoft.Assessments.Runtime.Support]::Initialize()
            if ( $axeSupport ) {

                $boolRunningUnderAxe = $axeSupport.EngineRunning

                $axeLogger = $axeSupport.CreateLogger()
            }
        }
    }

    # This function will write what is piped to it as a string to the default output
    # (usually the host/console) and also to the log file if running under axe.
    #
    function Log-Info {
        PROCESS {
            if ( $_ ) {
                "$_" | Out-Default
                if ( $axeLogger ) {
                    $axeLogger.LogMessage( "$_" )
                }
           }
        }
    }

    # This function will write what is piped to it as a string as an error
    # and also to the log file if running under axe.
    #
    function Log-Error([int32]$ErrorCode=1) {
        PROCESS {
            if ( $_ ) {
                "$_" | Write-Error
                if ( $axeLogger ) {
                    $axeLogger.LogError( $ErrorCode, "$_" )
                }
           }
        }
    }

    # Handle the special restart stuff.
    #
    if ( $Restart -eq $true ) {

        # We better have a restart count on the command line or we would just end up in a boot loop.
        #
        if ( $RestartCount -lt 0 ) {
            "Can not specify -Restart without also passing in a valid -RestartCount" | Log-Error
            Exit 1
        }

        "Restart Count=$RestartCount" | Log-Info

        # We can also only do restart if we are running in axe because it is what will launch us again.
        #
        if ( -not $boolRunningUnderAxe ) {
            "Can not specify -Restart unless running under the Axe framework." | Log-Error
            Exit 1
        }

        # We only have to do anything special if this is the first time the script was launched.
        #
        if ( $RestartCount -eq 0 ) {
            $axeSupport.ReportProgress( [Microsoft.Assessments.AxeProgressType]::OnOff, 0, "Rebooting to get clean system fundamental information." )
            Restart-Computer -Force
            Exit 0
        }
    } 

    # We want a list of PIDs to exclude because we don't want our test to affect the results.
    #
    $ExcludePid = @()

    # We always want to exclude our process.
    #
    $processThis = [System.Diagnostics.Process]::GetCurrentProcess()
    $ExcludePid += $processThis.Id

    # We also add our parent process ID if we are running in a framework.
    #
    if ( $boolRunningUnderAxe ) {

        # There is no easy way to get our "parent" process, so we load up this class which
        # will do some pinvoke magic to get the data we need.
        #
        Add-Type -Path (Join-Path $folderScript ProcessBasicInformation.cs)
        $processParent = [ProcessBasicInformation]::GetParentProcess()
        if ( $processParent ) {
            $ExcludePid += $processParent.Id
        }
    }

    # Create the results object from the sample in the script folder.
    #
    $xmlResults = [xml](Get-Content (Join-Path $folderScript results.xml))
    $metrics = $xmlResults.AssessmentResult.Iterations.Iteration.MetricValues.MetricValue

    # We are about to start capturing metrics... so we want to honor the delay now.
    #
    "Waiting $Delay seconds before capturing metrics..." | Log-Info
    Start-Sleep $Delay

    # Now build the list of processes, filtering out the ones we want to ignore.
    #
    $Processes = Get-Process | ? { -not ($ExcludePid -contains $_.Id) }
    $ProcessesExcluded = Get-Process | ? { $ExcludePid -contains $_.Id }

    $ProcessesExcluded | % { "Excluding PID #$($_.Id) '$($_.Name)' using $($_.WorkingSet) bytes." | Log-Info }

    # Save the total number of processess running.
    #
    $metrics | ? { $_.ProgrammaticName -eq "ProcessCount" } | % { $_.Value = $Processes.Count.ToString() }

    # Save the total working set used by all the processes.
    #
    $TotalWorkingSet = 0
    foreach ($Process in $Processes) { $TotalWorkingSet += $Process.WorkingSet }
    $memWorkingGB = $TotalWorkingSet / (1024*1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "ProcessWorkingSetGB" } | % { $_.Value = $memWorkingGB.ToString() }

    # We want to be able to add in the space used by our test and test framework.
    #
    $memExcludeAvailable = 0
    foreach ($Process in $ProcessesExcluded) { $memExcludeAvailable += $Process.WorkingSet }

    $wmiSystem = Get-WmiObject Win32_ComputerSystem
    $wmiMemory = Get-WmiObject Win32_PerfRawData_PerfOS_Memory
    $wmiOS = Get-WmiObject Win32_OperatingSystem

    $TotalMemoryGB = $wmiSystem.TotalPhysicalMemory / (1024*1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "TotalMemoryGB" } | % { $_.Value = $TotalMemoryGB.ToString() }

    $memStandby = $wmiMemory.StandbyCacheCoreBytes + $wmiMemory.StandbyCacheNormalPriorityBytes + $wmiMemory.StandbyCacheReserveBytes
    $memModified = $wmiMemory.ModifiedPageListBytes
    $memVisible = $wmiOS.TotalVisibleMemorySize * 1024
    $memAvailable = $wmiMemory.AvailableBytes + $memExcludeAvailable

    # I'm not sure about subtracting modified stuff, but task manager is doing something like that
    # and doing so gets me the same numbers (at least on my machine).
    #
    $MemoryUsedGB = ($memVisible - $memAvailable - $memModified ) / (1024*1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "MemoryUsedGB" } | % { $_.Value = $MemoryUsedGB.ToString() }

    $MemoryAvailableGB = $memAvailable / (1024*1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "MemoryAvailableGB" } | % { $_.Value = $MemoryAvailableGB.ToString() }

    $MemoryCommittedGB = $wmiMemory.CommittedBytes / (1024*1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "MemoryCommittedGB" } | % { $_.Value = $MemoryCommittedGB.ToString() }

    $MemoryCommitLimitGB = $wmiMemory.CommitLimit / (1024*1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "MemoryCommitLimitGB" } | % { $_.Value = $MemoryCommitLimitGB.ToString() }

    $MemoryCachedGB = ($memModified + $memStandby) / (1024*1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "MemoryCachedGB" } | % { $_.Value = $MemoryCachedGB.ToString() }

    $MemoryPagedPoolMB = $wmiMemory.PoolPagedBytes / (1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "MemoryPagedPoolMB" } | % { $_.Value = $MemoryPagedPoolMB.ToString() }

    $MemoryNonPagedPoolMB = $wmiMemory.PoolNonpagedBytes / (1024*1024)
    $metrics | ? { $_.ProgrammaticName -eq "MemoryNonPagedPoolMB" } | % { $_.Value = $MemoryNonPagedPoolMB.ToString() }

    # Print the metrics out.
    #
    $metrics | % { "Metric $($_.ProgrammaticName)=$($_.Value)" | Log-Info }
    if ( $ResultFile ) {
        $xmlResults.Save( $ResultFile )
    }

    #$Processes | sort company | format-Table ProcessName -groupby company


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

    # We should always properly dispose of this guy.
    #
    if ( $axeSupport ) {
        $axeSupport.Dispose()
    }

}