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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | |
})" |

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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |

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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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' | |
} | |
} |

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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:

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
)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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 | |
} |

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
)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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 | |
} |

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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for ($i = 0; $i -lt 10; $i++) { | |
[PSCustomObject]@{ | |
Date = Get-Date | |
ScriptBlock = 'Actual Scriptblock' | |
DateDescription = & $Script | |
} | |
Start-Sleep –Seconds 6 | |
} |

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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for ($i = 0; $i -lt 10; $i++) { | |
[PSCustomObject]@{ | |
Date = Get-Date | |
ScriptBlock = 'Actual Scriptblock' | |
DateDescription = $Script.Invoke() | |
} | |
Start-Sleep –Seconds 6 | |
} |

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
)

Seriously? It’s actually called InvokeReturnAsIs
? Let’s try it out
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for ($i = 0; $i -lt 10; $i++) { | |
[PSCustomObject]@{ | |
Date = Get-Date | |
ScriptBlock = 'Actual Scriptblock' | |
DateDescription = $Script.InvokeReturnAsIs() | |
} | |
Start-Sleep –Seconds 6 | |
} |

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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |

With the added benefit that I can include comment based help on the function!
Get-Help SecondsQuarter

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! 🙂
2 thoughts on “Using Scriptblocks in PSCustomObjects”