Output from function to console, preserving color - powershell

I'm running msbuild from powershell, inside a function:
function Run-MSBuild()
{
msbuild.exe (lots of params)
}
Run-MSBuild
Works great, and I see colored MSBuild output on my console. Now, I want to know how long it took:
$time = Measure-Command { Run-MSBuild }
Works, but I don't see the MSBuild output anymore.
Attempt 1: Pipe to Write-Host:
function Run-MSBuild()
{
msbuild.exe (lots of params) | Write-Host
}
Result 1: I see MSBuild output on my console, but color is lost.
Attempt 2: Replace Measure-Command:
$timer = [diagnostics.stopwatch]::startnew()
Run-MSBuild
$time = $timer.Elapsed
Result 2: Works, but kinda ugly.
Any way to achieve this?

Include the timing command in the function?
function Run-MSBuild
{
$start = Get-Date
msbuild.exe $args
$End = Get-Date
$End-$Start
}

Related

Powershell get return value from visual basic script

I have some old code in visual basic script. Instead of re-writing this old code into PowerShell, I'd like to call the VB scripts from PowerShell and capture their return value.
How can I get the return value of a visual basic script in powershell?
Something like this:
$returnValue = Invoke-Command -ScriptBlock{.\vbs\legacyVbsFunction.vbs}
The visual basic function may look like this
Function MyFunction() As Double
Return 3.87 * 2
End Function
It sounds like you want to capture a VBScript's (stdout) output:
$output = cscript.exe //nologo .\vbs\legacyVbsFunction.vbs
Note that $output will either be a single string - if the script outputs just 1 line - or an array of strings in case of multi-line output.
For example, assuming that .\vbs\legacyVbsFunction.vbs contains the following code:
Function MyFunction
MyFunction = 3.87 * 2
End Function
' Call the function and print it to stdout.
Wscript.Echo(MyFunction)
You could capture the output and convert it to a [double] as follows:
[double] $output = cscript.exe //nologo .\vbs\legacyVbsFunction.vbs
$output then contains 7.74.
You can actually embed a vbscript function right in powershell using a com object called ScriptControl, but it only works in 32-bit powershell, C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe: Embed VBS into PowerShell
function Call-VBScript
{
$sc = New-Object -ComObject ScriptControl
$sc.Language = 'VBScript'
$sc.AddCode('
Function MyFunction
MyFunction = 3.87 * 2
End Function
')
$sc.CodeObject
}
$vb = Call-VBScript
$returnvalue = $vb.MyFunction()
"returnvalue is " + $returnvalue
I found out you can run a job as 32-bit:
$returnvalue =
start-job {
function Call-VBScript {
$sc = New-Object -ComObject MSScriptControl.ScriptControl.1
$sc.Language = 'VBScript'
$sc.AddCode('
Function MyFunction
MyFunction = 3.87 * 2
End Function
')
$sc.CodeObject
}
$vb = call-vbscript
$vb.MyFunction()
} -runas32 | wait-job | receive-job
"returnvalue is " + $returnvalue
You don't really have an exit code in the VBS, you have a return for a function.
To actually have a return code you must close the script with something like:
wscript.quit(0)
or
wscript.quit(returncode)
In the powershell script you must execute the vbs something like this:
(Start-Process -FilePath "wscript.exe" -ArgumentList "Script.vbs" -Wait -Passthru).ExitCode

Simple PowerShell parallel command execution

I have two scripts I wish to run in parallel. I have a driver script I wish to use to execute the two parallel scripts.
The method I'm using of course does not run in parallel. I have looked all over the google universe, and at using a workflow, but I can't get that to work either.
Driver Script:
Clear-Host
$a = 4
$b = 29
$command1 = "C:\JUNK\Code\PowerScript\CS1.ps1 $a"
$command2 = "C:\JUNK\Code\PowerScript\CS2.ps1 $b"
#Invoke-Expression $command1
#Invoke-Expression $command2
The scripts to get called resemble this:
#CS1.ps1
Param(
[string]$build
)
$ADATE = Get-Date
$DESTLOG1="C:\JUNK\Code\PowerScript\LOGS\status1.log"
$RunDefintion1 = ($build, $ADATE -join",")
Add-Content $DESTLOG1 $RunDefintion1
From powershell 2 upwards you could use the Start-Job command, something like this ( untested, might need some adjustments )
Start-Job { C:\JUNK\Code\PowerScript\CS1.ps1 $a }
Start-Job { C:\JUNK\Code\PowerScript\CS2.ps1 $b }

Safe way to convert string literal without using Invoke-Expression [duplicate]

Imagine the following code:
# Script Start
$WelcomeMessage = "Hello $UserName, today is $($Date.DayOfWeek)"
..
..
# 100 lines of other functions and what not...
..
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage
}
This is a very basic example, but what it tries to show is a script where there is a $WelcomeMessage that the person running the script can set at the top of the script and controls how/what the message displayed is.
First thing's first: why do something like this? Well, if you're passing your script around to multiple people, they might want different messages. Maybe they don't like $($Date.DayOfWeek) and want to get the full date. Maybe they don't want to show the username, whatever.
Second, why put it at the top of the script? Simplicity. If you have 1000 lines in your script and messages like these spread all over the script, it makes it a nightmare for people to find and change these messages. We already do that for static messages, in the form of localized strings and stuff, so this is nothing new, except for the variable parts in it.
So, now to the issue. If you run that code and invoke Greet-User (assuming the functions/cmdlets for retrieving username and date actually exist and return something proper...) Greet-User will always return Hello , today is.
This is because the string is expanded when you declare it, at the top of the script, when neither $UserName nor $Date objects have a value.
A potential workaround would be to create the strings with single quotes, and use Invoke-Expression to expand them. But because of the spaces, that gets a bit messy. I.e.:
$WelcomeMessage = 'Hello $env:USERNAME'
Invoke-Expression $WelcomeMessage
This throws an error because of the space, to get it to work properly it would have to be declared as such:
$WelcomeMessage = 'Hello $env:USERNAME'
$InvokeExpression = "`"$WelcomeMessage`""
Messy...
Also, there's another problem in the form of code injection. Since we're allowing the user to write their own welcome message with no bounds specified, what's to prevent them from putting in something like...
$WelcomeMessage 'Hello $([void] (Remove-Item C:\Windows -Force -Recurse))'
(Yes, I know this will not delete everything but it is an example)
Granted this is a script and if they can modify that string they can also modify everything else on the script, but whereas the example I gave was someone maliciously taking advantage of the nature of the script, it can also happen that someone accidentally puts something in the string that ends up having unwanted consequences.
So... there's got to be a better way without the use of Invoke-Expression, I just can't quite thing of one so help would be appreciated :)
Embedding variables into strings is not the only way to create dynamic text, the way I would do it is like this:
$WelcomeMessage = 'Hello {0}, today is {1}'
# 100 lines of other functions and what not...
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage -f $Username, $Date
}
The canonical way to delay evaluation of expressions/variables in strings is to define them as single-quoted strings and use $ExecutionContext.InvokeCommand.ExpandString() later on.
Demonstration:
PS C:\> $s = '$env:COMPUTERNAME'
PS C:\> $s
$env:COMPUTERNAME
PS C:\> $ExecutionContext.InvokeCommand.ExpandString($s)
FOO
Applied to your sample code:
$WelcomeMessage = 'Hello $UserName, today is $($Date.DayOfWeek)'
...
...
...
function Greet-User {
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$ExecutionContext.InvokeCommand.ExpandString($WelcomeMessage)
}
Have you considered using a lambda expression; i.e. instead of defining the variable as a string value define it as a function, then invoke that function passing the relevant parameters at runtime.
$WelcomeMessage = {param($UserName,$Date);"Hello $UserName, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))"}
#...
# 100 lines of other functions and what not...
#...
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage.invoke($username,$date)
}
cls
Greet-User
Update
If you only wish to allow variable replacement the below code would do the trick; but this fails to do more advanced functions (e.g. .DayOfWeek)
$WelcomeMessage = 'Hello $Username, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))'
#...
# 100 lines of other functions and what not...
#...
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Resolve-WelcomeMessage(){
write-output {param($UserName,$Date);"$WelcomeMessage";}
}
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$temp = $WelcomeMessage
get-variable | ?{#('$','?','^') -notcontains $_.Name} | sort name -Descending | %{
$temp = $temp -replace ("\`${0}" -f $_.name),$_.value
}
$temp
}
cls
Greet-User
Update
To avoid code injection this makes use of -whatif; that will only help where the injected code supports the whatif functionality, but hopefully better than nothing...
Also the code now doesn't require parameters to be declared; but just takes those variables which are available at the time of execution.
$WelcomeMessage = {"Hello $Username, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))"}
#...
# 100 lines of other functions and what not...
#...
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Resolve-WelcomeMessage(){
write-output {param($UserName,$Date);"$WelcomeMessage";}
}
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Greet-User {
[cmdletbinding(SupportsShouldProcess=$True)]
param()
begin {$original = $WhatIfPreference; $WhatIfPreference = $true;}
process {
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
& $WelcomeMessage
}
end {$WhatIfPreference = $original;}
}
cls
Greet-User

Can I resolve PowerShell scriptblock parameters without invoking?

I'm looking at writing some PowerShell code that can either execute immediately, or produce the commands it would execute as generated scripts.
I'd like to avoid this scenario:
if($Generating){
write-Output "somecommand.exe"
}
else{
somecommand.exe
}
I got looking at ScriptBlocks, which at first looked promising because I can write the contents of the ScriptBlock to the console without executing it. Such as:
$sc = { somecommand.exe }
$sc
somecommand.exe
My specific question is, if my scriptblock contains parameters, can I get them to resolve when I'm writing the scriptblock contents to the console, but WITHOUT invoking the scriptblock?
For example given the following scriptblock:
$b2 = { Param([string]$P) Write-Host "$P" }
When I just type "$b2" at the console and hit enter I see this:
Param([string]$P) Write-Host "$P"
What I'd like to see is this (if the parameter value is "Foo"):
Param([string]$P) Write-Host "Foo"
I realize this can be done when it's invoked, either via "&" or using Invoke(), but would there be any way to get the parameters to resolve without invoking to make my script generation a little more elegant without needing a bunch of conditional statements throughout the code?
In PowerShell v3, you can get the param info via the AST property e.g.:
PS> $sb = {param($a,$b) "a is $a b is $b"}
PS> $sb.Ast.ParamBlock
Attributes Parameters Extent Parent
---------- ---------- ------ ------
{} {$a, $b} param($a,$b) {param($a,$b) "a...
Solution suitable for PowerShell v2:
# given the script block
$b2 = { Param([string]$P) Write-Host "$P" }
# make a function of it and "install" in the current scope
Invoke-Expression "function tmp {$b2}"
# get the function and its parameters
(Get-Command tmp).Parameters
When displaying a here-string with double quotes #" , it expands the variables. For the variables that should'nt expand, escape the variable with a backtick ( ` ).
So try this:
$P = "Foo"
$b2 = #"
{ Param([string]`$P) Write-Host "$P" }
"#
Test:
PS-ADMIN > $b2
{ Param([string]$P) Write-Host "Foo" }
If you want to convert it to scriptblock-type again:
#Convert it into scriptblock
$b3 = [Scriptblock]::Create($b2)
PS-ADMIN > $b3
{ Param([string]$P) Write-Host "Foo" }
PS-ADMIN > $b3.GetType().name
ScriptBlock
Using some of the suggestions I think I've found the best solution for my needs. Consider the following code
function TestFunc
{
Param(
[Parameter(Mandatory=$true)]
[string]$Folder,
[Parameter(Mandatory=$true)]
[string]$Foo
)
$code = #"
Write-Host "This is a folder $Folder"
Write-Host "This is the value of Foo $Foo"
"#
$block = [Scriptblock]::Create($code)
Write-Host "Running the block" -BackgroundColor Green -ForegroundColor Black
&$block
Write-Host "Displaying block code" -BackgroundColor Green -ForegroundColor Black
$block
}
And it's output:
Running the block
This is a folder c:\some\folder
This is the value of Foo FOOFOO
Displaying block code
Write-Host "This is a folder c:\some\folder"
Write-Host "This is the value of Foo FOOFOO"
By doing it this way, I still get all the benefit of keeping my existing functions and their parameters, parameter validation, CBH etc. I can also easily generate the code that the function would execute or just let it execute. Thanks for all the input, it's definitely been a good learning experience.
If you want to express your block as a block, not a string, the following works:
$printable = invoke-expression ('"' + ($block -replace '"', '`"') + '"')
Essentially, you're wrapping everything in quotes and then invoking it as an expression. The -replace call ensures any quotes in the block itself are escaped.
I'm using this in this handy function, which also halts execution if the invoked command failed.
# usage: exec { dir $myDir }
function exec($block)
{
# expand variables in block so it's easier to see what we're doing
$printable = invoke-expression ('"' + ($block -replace '"', '`"').Trim() + '"')
write-host "# $printable" -foregroundcolor gray
& $block
if ($lastExitCode -ne 0)
{
throw "Command failed: $printable in $(pwd) returned $lastExitCode"
}
}

Timing a command's execution in PowerShell

Is there a simple way to time the execution of a command in PowerShell, like the 'time' command in Linux?
I came up with this:
$s=Get-Date; .\do_something.ps1 ; $e=Get-Date; ($e - $s).TotalSeconds
But I would like something simpler like
time .\do_something.ps1
Yup.
Measure-Command { .\do_something.ps1 }
Note that one minor downside of Measure-Command is that you see no stdout output.
[Update, thanks to #JasonMArcher] You can fix that by piping the command output to some commandlet that writes to the host, e.g. Out-Default so it becomes:
Measure-Command { .\do_something.ps1 | Out-Default }
Another way to see the output would be to use the .NET Stopwatch class like this:
$sw = [Diagnostics.Stopwatch]::StartNew()
.\do_something.ps1
$sw.Stop()
$sw.Elapsed
You can also get the last command from history and subtract its EndExecutionTime from its StartExecutionTime.
.\do_something.ps1
$command = Get-History -Count 1
$command.EndExecutionTime - $command.StartExecutionTime
Use Measure-Command
Example
Measure-Command { <your command here> | Out-Host }
The pipe to Out-Host allows you to see the output of the command, which is
otherwise consumed by Measure-Command.
Simples
function time($block) {
$sw = [Diagnostics.Stopwatch]::StartNew()
&$block
$sw.Stop()
$sw.Elapsed
}
then can use as
time { .\some_command }
You may want to tweak the output
Here's a function I wrote which works similarly to the Unix time command:
function time {
Param(
[Parameter(Mandatory=$true)]
[string]$command,
[switch]$quiet = $false
)
$start = Get-Date
try {
if ( -not $quiet ) {
iex $command | Write-Host
} else {
iex $command > $null
}
} finally {
$(Get-Date) - $start
}
}
Source: https://gist.github.com/bender-the-greatest/741f696d965ed9728dc6287bdd336874
Using Stopwatch and formatting elapsed time:
Function FormatElapsedTime($ts)
{
$elapsedTime = ""
if ( $ts.Minutes -gt 0 )
{
$elapsedTime = [string]::Format( "{0:00} min. {1:00}.{2:00} sec.", $ts.Minutes, $ts.Seconds, $ts.Milliseconds / 10 );
}
else
{
$elapsedTime = [string]::Format( "{0:00}.{1:00} sec.", $ts.Seconds, $ts.Milliseconds / 10 );
}
if ($ts.Hours -eq 0 -and $ts.Minutes -eq 0 -and $ts.Seconds -eq 0)
{
$elapsedTime = [string]::Format("{0:00} ms.", $ts.Milliseconds);
}
if ($ts.Milliseconds -eq 0)
{
$elapsedTime = [string]::Format("{0} ms", $ts.TotalMilliseconds);
}
return $elapsedTime
}
Function StepTimeBlock($step, $block)
{
Write-Host "`r`n*****"
Write-Host $step
Write-Host "`r`n*****"
$sw = [Diagnostics.Stopwatch]::StartNew()
&$block
$sw.Stop()
$time = $sw.Elapsed
$formatTime = FormatElapsedTime $time
Write-Host "`r`n`t=====> $step took $formatTime"
}
Usage Samples
StepTimeBlock ("Publish {0} Reports" -f $Script:ArrayReportsList.Count) {
$Script:ArrayReportsList | % { Publish-Report $WebServiceSSRSRDL $_ $CarpetaReports $CarpetaDataSources $Script:datasourceReport };
}
StepTimeBlock ("My Process") { .\do_something.ps1 }
All the answers so far fall short of the questioner's (and my) desire to time a command by simply adding "time " to the start of the command line. Instead, they all require wrapping the command in brackets ({}) to make a block. Here is a short function that works more like time on Unix:
Function time() {
$command = $args -join ' '
Measure-Command { Invoke-Expression $command | Out-Default }
}
A more PowerShell inspired way to access the value of properties you care about:
$myCommand = .\do_something.ps1
Measure-Command { Invoke-Expression $myCommand } | Select -ExpandProperty Milliseconds
4
As Measure-Command returns a TimeSpan object.
note: The TimeSpan object also has TotalMilliseconds as a double (such as 4.7322 TotalMilliseconds in my case above) which might be useful to you. Just like TotalSeconds, TotalDays, etc.
(measure-commmand{your command}).totalseconds
for instance
(measure-commmand{.\do_something.ps1}).totalseconds
Just a word on drawing (incorrect) conclusions from any of the performance measurement commands referred to in the answers. There are a number of pitfalls that should taken in consideration aside from looking to the bare invocation time of a (custom) function or command.
Sjoemelsoftware
'Sjoemelsoftware' voted Dutch word of the year 2015
Sjoemelen means cheating, and the word sjoemelsoftware came into being due to the Volkswagen emissions scandal. The official definition is "software used to influence test results".
Personally, I think that "Sjoemelsoftware" is not always deliberately created to cheat test results but might originate from accommodating practical situation that are similar to test cases as shown below.
As an example, using the listed performance measurement commands, Language Integrated Query (LINQ)(1), is often qualified as the fasted way to get something done and it often is, but certainly not always! Anybody who measures a speed increase of a factor 40 or more in comparison with native PowerShell commands, is probably incorrectly measuring or drawing an incorrect conclusion.
The point is that some .Net classes (like LINQ) using a lazy evaluation (also referred to as deferred execution(2)). Meaning that when assign an expression to a variable, it almost immediately appears to be done but in fact it didn't process anything yet!
Let presume that you dot-source your . .\Dosomething.ps1 command which has either a PowerShell or a more sophisticated Linq expression (for the ease of explanation, I have directly embedded the expressions directly into the Measure-Command):
$Data = #(1..100000).ForEach{[PSCustomObject]#{Index=$_;Property=(Get-Random)}}
(Measure-Command {
$PowerShell = $Data.Where{$_.Index -eq 12345}
}).totalmilliseconds
864.5237
(Measure-Command {
$Linq = [Linq.Enumerable]::Where($Data, [Func[object,bool]] { param($Item); Return $Item.Index -eq 12345})
}).totalmilliseconds
24.5949
The result appears obvious, the later Linq command is a about 40 times faster than the first PowerShell command. Unfortunately, it is not that simple...
Let's display the results:
PS C:\> $PowerShell
Index Property
----- --------
12345 104123841
PS C:\> $Linq
Index Property
----- --------
12345 104123841
As expected, the results are the same but if you have paid close attention, you will have noticed that it took a lot longer to display the $Linq results then the $PowerShell results.
Let's specifically measure that by just retrieving a property of the resulted object:
PS C:\> (Measure-Command {$PowerShell.Property}).totalmilliseconds
14.8798
PS C:\> (Measure-Command {$Linq.Property}).totalmilliseconds
1360.9435
It took about a factor 90 longer to retrieve a property of the $Linq object then the $PowerShell object and that was just a single object!
Also notice an other pitfall that if you do it again, certain steps might appear a lot faster then before, this is because some of the expressions have been cached.
Bottom line, if you want to compare the performance between two functions, you will need to implement them in your used case, start with a fresh PowerShell session and base your conclusion on the actual performance of the complete solution.
(1) For more background and examples on PowerShell and LINQ, I recommend tihis site: High Performance PowerShell with LINQ
(2) I think there is a minor difference between the two concepts as with lazy evaluation the result is calculated when needed as apposed to deferred execution were the result is calculated when the system is idle