Test-Expression.psm1

#requires -version 4.0

#an internal function for the actual testing
Function _TestMe {
[cmdletbinding(DefaultParameterSetName="Interval")]
Param(
[scriptblock]$Expression,
[object[]]$ArgumentList,
[ValidateScript({$_ -ge 1})]
[int]$Count = 1,
[Parameter(ParameterSetName = "Interval")]
[ValidateRange(0,60)]
[double]$Interval=.5,
[Parameter(ParameterSetName = "Random",Mandatory)]
[Alias("min")]
[double]$RandomMinimum,
[Parameter(ParameterSetName = "Random",Mandatory)]
[Alias("max")]
[double]$RandomMaximum,
[switch]$IncludeExpression
)

 $TestData = 1..$count | foreach -begin {
     <#
      PowerShell doesn't seem to like passing a scriptblock as an
      argument when using Invoke-Command. It appears to pass it as
      a string so I'm recreating it as a scriptblock here.
     #>

     $script:testblock = [scriptblock]::Create($Expression)
     
     } -process {
         #invoke the scriptblock with any arguments and measure
        Measure-Command -Expression {$($script:testblock).Invoke(@($argumentlist)) } -OutVariable +out
        
     #} -outvariable +out
     #pause to mitigate any caching effects
     if ($RandomMinimum -AND $RandomMaximum) {
        $sleep = Get-Random -Minimum ($RandomMinimum*1000) -Maximum ($RandomMaximum*1000)
        $TestInterval = "Random"
     }   
     else {
        $Sleep = ($Interval*1000)
        $TestInterval = $Sleep
     } 
     
     Start-Sleep -Milliseconds $sleep
    } 
    
    $TestResults = $TestData | 
    Measure-Object -Property TotalMilliseconds -Average -Maximum -Minimum |
    Select-Object -Property @{Name = "Tests";Expression={$_.Count}},
    @{Name = "TestInterval";Expression = {$TestInterval}},
    @{Name = "AverageMS";Expression = {$_.Average}},
    @{Name = "MinimumMS";Expression = {$_.Minimum}},
    @{Name = "MaximumMS";Expression = {$_.Maximum}},
    @{Name = "MedianMS";Expression = {
        #sort the values to calculate the median and trimmed values
        $sort = $out.totalmilliseconds | sort

        #test if there are an even or odd number of elements
        if ( ($sort.count) %2) {
            #odd number
            #subtract 1 because arrays start counting at 0
            $sort[(($sort.count-1)/2) -as [int]]
        }
        else {
            #even number
            #get middle two numbers and their average
            ($sort[($sort.count/2)] + $sort[$sort.count/2+1])/2
        }        
    }},
    @{Name="TrimmedMS";Expression={
        #values must be sorted in ascending order
        $data = $out.totalmilliseconds | Sort
        #select elements from the second to next to last
        ($data[1..($data.count-2)] | Measure-Object -Average).Average
  
    }}     
    
    #add metadata
    $OS = Get-Ciminstance -ClassName win32_operatingsystem 
    $TestResults | Add-Member -MemberType Noteproperty -Name PSVersion -Value $PSVersionTable.PSVersion.ToString()  
    $TestResults | Add-Member -MemberType Noteproperty -Name OS -Value $OS.caption

    if ($IncludeExpression) {
        Write-Verbose "Adding expression to output"
        $TestResults | Add-Member -MemberType Noteproperty -Name Expression -Value $Expression
        $TestResults | Add-Member -MemberType Noteproperty -Name Arguments -Value $ArgumentList
    }    

    Write-Verbose "Inserting a new type name"
    $TestResults.psobject.typenames.insert(0,"my.TestResult")

    #write the result to the pipeline
    $testResults
} #_TestMe function

#exposed functions
Function Test-Expression {

[cmdletbinding(DefaultParameterSetName="Interval")]
Param(
[Parameter(
    Position = 0,
    Mandatory,
    HelpMessage = "Enter a scriptblock to test",
    ValueFromPipeline
    )]
[Alias("sb")]
[scriptblock]$Expression,

[object[]]$ArgumentList,

[Parameter(ValueFromPipelineByPropertyName)]
[ValidateScript({$_ -ge 1})]
[int]$Count = 1,

[Parameter(
ParameterSetName = "Interval",
ValueFromPipelineByPropertyName)]
[ValidateRange(0,60)]
[Alias("sleep")]
[double]$Interval = .5,

[Parameter(
    ParameterSetName = "Random",
    Mandatory
    )]
[Alias("min")]
[double]$RandomMinimum,

[Parameter(
    ParameterSetName = "Random",
    Mandatory
    )]
[Alias("max")]
[double]$RandomMaximum,

[Parameter(ValueFromPipelineByPropertyName)]
[Alias("ie")]
[switch]$IncludeExpression,

[switch]$AsJob

)

Write-Verbose "Starting: $($MyInvocation.Mycommand)"
Write-Verbose ($PSBoundParameters | Out-string)
Write-Verbose "Measuring expression:"
Write-Verbose ($Expression | Out-String)
if ($ArgumentList) {
    Write-Verbose "Arguments: $($ArgumentList -join ",")"
}

if ($PSCmdlet.ParameterSetName -eq 'Interval') {
    write-Verbose "$Count time(s) with a sleep interval of $interval seconds."
}
else {
    write-Verbose "$Count time(s) with a random sleep interval between $RandomMinimum seconds and $RandomMaximum seconds."
}


If ($AsJob) {
    Write-Verbose "Running as a background job"
    $PSBoundParameters.remove("AsJob") | Out-Null
    start-job -ScriptBlock {
    Param([hashtable]$Testparams)
    
    <#
      PowerShell doesn't seem to like passing a scriptblock as an
      argument when using Invoke-Command. It appears to pass it as
      a string so I'm recreating it as a scriptblock here.
    #>

 
    $expression = [scriptblock]::Create($Testparams.Expression)
    $TestParams.Expression = $Expression
    Test-Expression @testparams 
    } -ArgumentList @($PSBoundParameters) -InitializationScript {Import-Module Test-Expression}

}
else {

   $PSBoundParameters.remove("AsJob") | Out-Null
   _TestMe @PSBoundParameters
}

Write-Verbose "Ending: $($MyInvocation.Mycommand)"

} #end function

Function Test-ExpressionForm {
[cmdletbinding()]
Param()

Add-Type -AssemblyName PresentationFramework

[xml]$xaml = Get-Content $psscriptroot\form.xaml

$reader = New-Object system.xml.xmlnodereader $xaml
$form = [windows.markup.xamlreader]::Load($reader)

$sb = $form.FindName("txtScriptBlock")
$count = $form.FindName("txtCount")
$results = $form.FindName("tbResults")
$slider = $form.Findname("sliderStatic")
$radioStatic = $form.FindName("radioStatic")
$radioRandom = $form.FindName("radioRandom")
$min = $form.FindName("txtMin")
$Max = $form.FindName("txtMax")
$argumentList = $form.FindName("txtArguments")
$run = $form.FindName("btnRun")
$quit = $form.Findname("btnQuit")

#defaults
$min.Text = 1
$max.text = 5
$min.IsEnabled = $False
$max.IsEnabled = $false
$slider.IsEnabled = $True

$radioStatic.add_Checked({
    $min.IsEnabled = $False
    $max.IsEnabled = $false
    $slider.IsEnabled = $True
})

$radioRandom.Add_checked({
    $min.IsEnabled = $True
    $max.IsEnabled = $True
    $slider.IsEnabled = $False
})

$quit.add_click({ $form.close() })

$run.add_click({

#uncomment for troubleshooting
#write-host "running" -ForegroundColor green

if ($sb.Text -notmatch "\w") {
    Write-Warning "You must enter something to test!"
    Return
}

$params = @{
 Expression = [scriptblock]::Create($sb.text)
 Count = $count.text -as [int]
 IncludeExpression = $True
}

If ($argumentList.text) {
    $params.Add("ArgumentList",($argumentList.Text -split ","))
}

if ($radioStatic.IsChecked) {
    $interval = [math]::round($slider.value,1)
    $params.Add("interval",$interval)
}
else {
    [double]$minimum = [math]::Round($min.Text,1)
    [double]$maximum = [math]::Round($max.text,1)
    $params.Add("RandomMinimum",$minimum)
    $params.Add("RandomMaximum",$maximum)
}

$form.Cursor = [System.Windows.Input.Cursors]::Wait
#uncomment for troubleshooting
#$params | out-string | write-host -ForegroundColor cyan

$script:out = Test-Expression @params

$results.text = ($script:out | Select * -exclude OS,Expression,Arguments | Out-String).Trim()
$form.Cursor = [System.Windows.Input.Cursors]::Default
})

$sb.Focus() | Out-Null
$form.ShowDialog() | Out-Null

#write the current results to the pipeline after the form is closed.
$script:out
}


#define an optional alias
Set-Alias -Name tex -Value Test-Expression
Set-Alias -Name texf -Value Test-ExpressionForm