2

Basically I'm trying to get the below "inline if-statement" function working (credit here)

Function IIf($If, $Then, $Else) {
    If ($If -IsNot "Boolean") {$_ = $If}
    If ($If) {If ($Then -is "ScriptBlock") {&$Then} Else {$Then}}
    Else {If ($Else -is "ScriptBlock") {&$Else} Else {$Else}}
}

Using PowerShell v5 it doesn't seem to work for me and calling it like

IIf "some string" {$_.Substring(0, 4)} "no string found :("

gives the following error:

You cannot call a method on a null-valued expression.
At line:1 char:20
+ IIf "some string" {$_.Substring(0, 4)} "no string found :("
+                    ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

So, as a more general question, how do you make $_ available to the scriptblock passed into a function?

I kind of tried following this answer, but it seems it's meant for passing it to a separate process, which is not what I'm looking for.

Update: It seems the issue is that I have the function in a module rather than directly in a script/PS session. A workaround would be to avoid putting it in the module, but I feel a module is more portable, so I'd like to figure out a solution for that.

Xerillio
  • 3,729
  • 1
  • 14
  • 24
  • 2
    If have tested, PowerShell 5, Core and ISE and they all give the expected results: `Some`. I can't explain why you get this error. Have you tried a new PowerShell session? – iRon Feb 14 '19 at 19:15
  • I too get *some* in powershell.exe as host and 5.1 as version – vrdse Feb 14 '19 at 19:40
  • What happens when use the `$If` variable rather then the automatic variable `$_` in your `Else` scriptblock? Thus: `{$If.Substring(0, 4)}` – iRon Feb 15 '19 at 07:41
  • @iRon thanks for testing it out. If I add the function directly to the session it works now. I had it in a module before which seems to cause the difference. I'll update the question with that info... I wasn't aware that made a difference. So, I'm still looking for a solution. – Xerillio Feb 15 '19 at 08:21

1 Answers1

2

While I have no explanation for your symptom, there are two changes worth making:

  • Do not try to assign to $_ directly; it is an automatic variable under PowerShell's control, not meant to be set by user code (even though it may work, it sholdn't be relied upon).

    • Instead, use the ForEach-Object cmdlet to implicitly set $_ via its -InputObject parameter.
  • Use the -is operator with type literals such as [Boolean], not type names such as "Boolean".

Function IIf($If, $Then, $Else) {
  If ($If) { 
    If ($Then -is [scriptblock]) { ForEach-Object -InputObject $If -Process $Then } 
    Else { $Then } 
  } Else {
    If ($Else -is [scriptblock]) { ForEach-Object -InputObject $If -Process $Else }
    Else { $Else }
  }
}
mklement0
  • 312,089
  • 56
  • 508
  • 622
  • Thanks for the comment, in fact the automatic variable is a little overdone, because you could reuse the input variable (`$If`) as well (just one character more). – iRon Feb 15 '19 at 07:48
  • Thanks for the suggestions, however `ForEach-Object` will apply `$Then` to every object in an array, which is not the intention. Also interesting you mention using type literals, which I think looks much neater in code, but I stumbled upon [this blog post](https://www.adamtheautomator.com/wary-powershells-outputtype-keyword/) that suggests using strings as a general rule of thumb. Of course that's only really necessary for non-standard .NET types – Xerillio Feb 15 '19 at 08:27
  • @iRon: Relying on `$If` means relying on an _implementation detail_, namely the name of the parameter variable. While you could make that part of the "contract" of your function, it is far from obvious and runs counter to the well-established semantics of using `$_` to refer to the input object at hand. – mklement0 Feb 15 '19 at 11:37
  • @Xerillio: No, due to use of `-InputObject` rather than the pipeline, an array is bound _as a whole_ to `$_` - try `iif (1,2) { "[$_]" } 'unused'`. – mklement0 Feb 15 '19 at 11:42
  • 1
    @Xerillio: As for using type literals: The blog post you link to discusses a _parse-time_ problem related to the `[OutputType]` attribute used in the definition of advanced functions, with respect to types that aren't loaded into the session until _runtime_. This is _not_ a problem here, and use of type literals is generally preferable. – mklement0 Feb 15 '19 at 11:47
  • 1
    @mklement0 ah, you're absolutely right. Thanks, that seems to work even when `IIf` is part of a module. And yeah regarding type literals, I guess I was thinking more about making it a habit to avoid making that mistake without thinking. But type literals is a better option in the general sense like you say. – Xerillio Feb 15 '19 at 11:54
  • Good point re definition in a module, Xerillio; @iRon, that's another reason not to use `$If`: if the `IIf` function is defined in a module and you call it from outside that module, script blocks you pass to it won't see the now module-scoped `$If` variable. – mklement0 Feb 15 '19 at 12:09