user736893
user736893

Reputation:

Measure runtime of start-process with parameters

I have a script that runs the following commands:

$result = Start-Process -Wait -PassThru $proc $args

I want to measure how long it takes. Based on this article, I am trying the following:

$buildtime = measure-command -expression { $result = Start-Process -Wait -PassThru $proc $args }

Unfortunately, it throws the following error:

Start-Process : Cannot validate argument on parameter 'ArgumentList'. The argument is null, empty, or an element of
the argument collection contains a null value. Supply a collection that does not contain any null values and then try
the command again.
At C:\!scripts\pullandbuild.ps1:78 char:90
+ ... d -expression { $result = Start-Process -Wait -PassThru $proc $args }
+                                                                   ~~~~~
    + CategoryInfo          : InvalidData: (:) [Start-Process], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.StartProcessCommand

Due to other issues I had with strings and variable expansion, $args is defined like this:

$args = @"
& -quit -batchmode -logFile "$logfile" -buildWindows64Player "$($fullpath)\Project.exe"
"@

How can I fix this? Or is there another way to capture the runtime of the script? I'm trying to learn PS so I'd rather avoid just capturing the time before I run it and after I run and comparing, but if that's my only option I will.

Upvotes: 2

Views: 1181

Answers (3)

mklement0
mklement0

Reputation: 440317

stivix's answer contains the crucial pointer - $args is an automatic variable that shouldn't be used as a custom variable - but it's worth digging deeper:

$args is automatically set to the array ([object[]]) of positional arguments passed to a simple script [block] or function[1], inside that code unit; if no arguments are passed, $args contains an empty array.

Since you cannot pass arguments intended to be seen by a script block passed to Measure-Command's -Expression parameter, $args is by definition the empty array inside that script block, which caused Start-Process to fail.

As an aside: That Start-Process fails with an empty array - instead of interpreting it as "no arguments to pass" - is a problem in Windows PowerShell that has since been corrected in PowerShell Core.

That PowerShell even lets you assign a value to $args is problematic. Unfortunately, this is part of a larger problem of not preventing custom use of automatic variables - see this answer.

The insidious aspect of this behavior is that the problem doesn't surface until you enter a script [block] or function; in the caller's scope the custom value assigned to $args does take effect, as the following example shows:

$args = 'hi'  # !! To be avoided. Assign a custom value to $args
# Echo the custom value, in the same scope.
"after assignment: [$args]"         
# Call a script block and echo $args there. 
& { "in script block: [$args]; element count: $($args.Count)" }  

The above yields:

after assignment: [hi]
in script block: []; element count: 0

The 1st output line shows that the assignment in the caller's scope worked as expected.

The 2nd output line shows that $args was automatically set to an empty array upon entering the script block, because no arguments were passed to the script block ({ ... }).

Note: Technically, a local $args variable is created inside the script block, and the caller's copy is still accessible if you access the caller's scope explicitly (e.g. via (Get-Variable args -Scope 1 -ValueOnly)), but the more sensible approach is to avoid custom use of $args altogether.


[1] $args only contains those arguments that aren't bound to explicitly declared parameters, if any. In advanced script or functions, you may only ever pass arguments to explicitly defined parameters, so $args is not defined there, because its use would be pointless; in fact, in an advanced script / function $args does retain a caller's custom $args definition if run in the same scope domain, but that shouldn't be relied upon.

Upvotes: 2

stivix
stivix

Reputation: 31

Edit: I just checked, your $args variable is empty, turns out $args is a built-in variable. Once I changed variable name, it's not null anymore, try that.

How about you try one of these approaches:

1.

$startTime = Get-Date
$processToRun = Start-Process notepad.exe -PassThru -Wait
$finishTime = Get-Date
$timeDifference = New-TimeSpan -Start $startTime -End $finishTime
  1. Use StartTime and ExitTime properties of $result, then compare times using New-Timespan

Upvotes: 3

santhosh
santhosh

Reputation: 1

To put quotes:

"Start-Process -Wait -PassThru $proc $args"

should be:

$buildtime = measure-command -expression {$results = "Start-Process -Wait -PassThru $proc $args"}

and

'@ & -quit -batchmode "-logFile $logfile" -buildWindows64Player "$($fullpath)\Project.exe"@'

Upvotes: -1

Related Questions