Using Scriptblocks in PSCustomObjects

Self-documenting so I know how to do this myself later!

Words: 806

Time to read: ~ 4 minutes

tl;dr: Save your command as a [scriptblock] variable and then use .InvokeReturnAsIs method

Update: 2018-04-24 Added examples of using simple functions as well.

I am starting to believe that I’ve gone through 3 phases of returning information from the PowerShell console:

(1). Using Write-Host to return information


Write-Host "Date: $(Get-Date)"
Write-Host "ScriptBlock: Write-Host"
Write-Host "DateDescription: $(if ((Get-Date).Second -lt 15) {
'1st Quarter'
} elseif ((Get-Date).Second -lt 30) {
'2nd Quarter'
} elseif ((Get-Date).Second -lt 45) {
'3rd Quarter'
} else {
'4th Quarter'
})"

01_WriteHost
Also, hard to tell the difference if you’re running it as a script

which, while returning what you want, just returns them to the screen. Not much formatting, can’t be used in a pipeline, etc, etc.

(2). Using Write-Output or Write-Information to return information


# So we can "see" the Write-Information
$OldInformationPreference = $InformationPreference
$InformationPreference = "Continue"
Write-Information "Date: $(Get-Date)"
Write-Information "ScriptBlock: Write-Information"
Write-Information "DateDescription: $(if ((Get-Date).Second -lt 15) {
'1st Quarter'
} elseif ((Get-Date).Second -lt 30) {
'2nd Quarter'
} elseif ((Get-Date).Second -lt 45) {
'3rd Quarter'
} else {
'4th Quarter'
})"
#Reset back
$InformationPreference = $OldInformationPreference

02_WriteInformation
Why am I starting to feel like “Goldilocks”…

Now, while this is better, it still suffers from the same problem as before with the results being individual lines and not really connecting with each other. So we move on to the 3rd option…

(3). Using [PSCustomObject] to return information


[PSCustomObject]@{
Date = Get-Date
Scriptblock = 'PSCustomObject'
DateDescription = if ((Get-Date).Second -lt 15) {
'1st Quarter'
} elseif ((Get-Date).Second -lt 30) {
'2nd Quarter'
} elseif ((Get-Date).Second -lt 45) {
'3rd Quarter'
} else {
'4th Quarter'
}
}

03_PSCustomObject
This output is just right!

Finally we have output that is an object! So it is connected, and can be used later on in the pipeline, etc.

Making It Flat

Probably one of the most influential PowerShell posts that I read when starting PowerShell was from Mark Kraus ( blog | twitter ) and it’s called “Flat Is Justice! Flatter Code for PowerShell

That was instrumental in moving from Write-Host to Write-Information to PSCustomObject as it helped to “flatten” my code.

And, if we look at our PSCustomObject code, it’s not exactly flat is it?

I can let it go once but the script that I was working on called the PSCustomObject in 5 or 6 different places. That’s too much to leave it as it is. It needs to be flattened.

So what can we do with this?…

Manually Typed


for ($i = 0; $i -lt 10; $i++) {
[PSCustomObject]@{
Date = Get-Date
ScriptBlock = 'Manually typed'
DateDescription = if ((Get-Date).Second -lt 15) {
'1st Quarter'
}
elseif ((Get-Date).Second -lt 30) {
'2nd Quarter'
}
elseif ((Get-Date).Second -lt 45) {
'3rd Quarter'
}
else {
'4th Quarter'
}
}
Start-Sleep Seconds 6
}

I mean, it runs fine; nothing wrong there:

ManuallyTyped
Loooong screenshot
We need to think of a way to flatten it out though. If there was only a way that we could take the code out into a string and then run it later…
Well let’s find out.

String

Let us try and do exactly what we just said. Take our command out as a string and try to run it later on.

Luckily, we can take advantage of the Invoke-Expression cmdlet on our string to run it exactly how it is! (Get-Help Invoke-Expression -Full)


$CommandString = @'
if ((Get-Date).Second -lt 15) {
'1st Quarter'
} elseif ((Get-Date).Second -lt 30) {
'2nd Quarter'
} elseif ((Get-Date).Second -lt 45) {
'3rd Quarter'
} else {
'4th Quarter'
}
'@
for ($i = 0; $i -lt 10; $i++) {
[PSCustomObject]@{
Date = Get-Date
ScriptBlock = 'Invoke-Expression'
DateDescription = Invoke-Expression Command $CommandString
}
Start-Sleep Seconds 6
}

Invoke-Expression
Short and sweet!

Now, our [PSCustomObject] is only 5 lines….

But I don’t really like this.

If there is a PowerShell version of SQL Injection, this feels like it would be rife with problems. Is there anything else that we can do?

ScriptBlock

How about we don’t store it as a string, but instead store it as the type that it should be; a scriptblock.

Put our code in curly brackets, define the type and we’re good to go! (Get-Help about_Script_Blocks -Full)


[Scriptblock]$Script = {
if ((Get-Date).Second -lt 15) {
'1st Quarter'
}
elseif ((Get-Date).Second -lt 30) {
'2nd Quarter'
}
elseif ((Get-Date).Second -lt 45) {
'3rd Quarter'
}
else {
'4th Quarter'
}
}
for ($i = 0; $i -lt 10; $i++) {
[PSCustomObject]@{
Date = Get-Date
ScriptBlock = 'Actual Scriptblock'
DateDescription = Invoke-Command Script $Script
}
Start-Sleep Seconds 6
}

view raw

Scriptblock.ps1

hosted with ❤ by GitHub

Invoke-Command
Didn’t I just run this?

IT WORKS!…and yet… Invoke-Command -Script $Script is awfully long. Isn’t there a better way? Well there was in the help file, using the ampersand (&) symbol


for ($i = 0; $i -lt 10; $i++) {
[PSCustomObject]@{
Date = Get-Date
ScriptBlock = 'Actual Scriptblock'
DateDescription = & $Script
}
Start-Sleep Seconds 6
}

AmpersandOperator
AND…..go!

This…also…works… but dammit, who’s going to know what that means? I want this to be readable and easy to troubleshoot!

Stumbling along

Hmmm, reading from the help file on Invoke-Command

For more information about the call operator, see about_Operators

Okay so, let’s Get-Help about_Operators -Full then!

Runs a command, script, or script block. The call operator, also known as
the “invocation operator,” lets you run commands that are stored in
variables and represented by strings. Because the call operator does not
parse the command, it cannot interpret command parameters.

Well that wasn’t much help.

It’s also called an “invocation operator” huh? If only there was something like an .Invoke() method for this guy, then I could do something like the following:


for ($i = 0; $i -lt 10; $i++) {
[PSCustomObject]@{
Date = Get-Date
ScriptBlock = 'Actual Scriptblock'
DateDescription = $Script.Invoke()
}
Start-Sleep Seconds 6
}

InvokeMethod
With a side of curly fries…I mean brackets

That works??!!  How does that work, I made it up?!

Well, it slightly works… Why are there curly brackets around my results? Why does that even work at all? Why can’t I just return the results as is?

Ah damn, I’ve committed one of the cardinal sins of PowerShell. I haven’t run Get-Member. ($Script | Get-Member)

GetMemberResult
It even has properties as well as methods!

Seriously? It’s actually called InvokeReturnAsIs? Let’s try it out


for ($i = 0; $i -lt 10; $i++) {
[PSCustomObject]@{
Date = Get-Date
ScriptBlock = 'Actual Scriptblock'
DateDescription = $Script.InvokeReturnAsIs()
}
Start-Sleep Seconds 6
}

InvokeReturnAsIs
Flat and fab!

Perfect! That’s exactly what I wanted! Clear, concise, readable, easy to troubleshoot, and flat!

UPDATE:

After publishing this, I got informed of another way that I thought was a great addition; functions (thank you Mr purplemonkeymad, whoever you are)


function SecondsQuarter {
<#
.SYNOPSIS
returns the "quarter" based on the current time's seconds
.DESCRIPTION
Returns either "1st quarter", "2nd quarter", "3rd quarter", or "4th quarter" depending on the current time.
00-14: 1st quarter
15-29: 2nd quarter
30-44: 3rd quarter
45-59: 4th quarter
.EXAMPLE
PS C:\> SecondsQuarter
.INPUTS
None
.OUTPUTS
[System.String]
.NOTES
Can use this instead of a scriptblock
#>
if ((Get-Date).Second -lt 15) {
'1st Quarter'
}
elseif ((Get-Date).Second -lt 30) {
'2nd Quarter'
}
elseif ((Get-Date).Second -lt 45) {
'3rd Quarter'
}
else {
'4th Quarter'
}
}
for ($i = 0; $i -lt 10; $i++) {
[PSCustomObject]@{
Date = Get-Date
ScriptBlock = 'Actual Scriptblock'
DateDescription = SecondsQuarter
}
Start-Sleep Seconds 6
}

function
I feel the power of knowledge!

With the added benefit that I can include comment based help on the function!

Get-Help SecondsQuarter

CommentBasedHelp
:heart-eyes:

In the end

This was a strange day figuring this out but now that I know I can invoke commands in PSCustomObject objects, I feel like I’ll be able to do a lot more with my PowerShell.

I’m self-documenting this here for later! 🙂

Author: Shane O'Neill

DBA, T-SQL and PowerShell admirer, Food, Coffee, Whiskey (not necessarily in that order)...

2 thoughts on “Using Scriptblocks in PSCustomObjects”

Leave a Reply

%d bloggers like this: