1

I'm trying to return TRUE from searching Get-ComplianceSearch's output for 'Completed'. My code below is a simple wait loop. But I don't think I'm returning the value correctly because the loop never finishes. I'm fairly new to PowerShell. Please assist or direct.

I'm using Powershell Core 7.1. There are no errors but the Search-String condition never returns TRUE.

    try {
        $timer = [Diagnostics.Stopwatch]::StartNew()
        while (($timer.Elapsed.TotalSeconds -lt $Timeout) -and (-not (Get-ComplianceSearch - 
Identity $searchName | Select-String 'Completed' -SimpleMatch -Quiet))) {
        Start-Sleep -Seconds $RetryInterval
        $totalSecs = [math]::Round($timer.Elapsed.TotalSeconds, 0)
        Write-Verbose -Message "Still waiting for action to complete after [$totalSecs] 
seconds..."
        }
    $timer.Stop()
    if ($timer.Elapsed.TotalSeconds -gt $Timeout) {
        throw 'Action did not complete before timeout period.'
    } else {
        Write-Verbose -Message 'Action completed before timeout period.'
    }
} catch {
    Write-Error -Message $_.Exception.Message
}

(This is the expected output of the command Get-ComplianceSearch)

enter image description here

codewario
  • 19,553
  • 20
  • 90
  • 159
Ccorock
  • 892
  • 12
  • 37

2 Answers2

2

Bender the Greatest's helpful answer shows a better alternative to using Select-String, because OO-based filtering that queries specific properties is always more robust than searching string representations.

That said, for quick-and-dirty interactive searches, being able to search through a command's formatted display output can be handy, and, unfortunately, Select-String does not do that by default.


As for what you tried:

To make your Select-String work, you need to insert Out-String -Stream before the Select-String call, so as to ensure that the for-display representation is sent through the pipeline, line by line.

# `oss` can be used in lieu of `Out-String -Stream` in PSv5+.
# `sls` can be used in lieu of `Select-String`.
Get-ComplianceSearch | Out-String -Stream | Select-String 'Completed' -SimpleMatch -Quiet

Note:

  • If you want to search a for-display representation other than the default one, you can insert a Format-* cmdlet call before the Out-String -Stream segment; e.g.
    Get-Item / | Format-List * | Out-String -Stream | Select-String ... would search through a list representation of all properties of the object output by Get-Item.

Perhaps surprisingly, Select-String does not search an input object's for-display representation, as you would see it in the console, using the rich formatting provided by PowerShell's display-formatting system.

Instead, it performs simple .ToString() stringification, whose results are often unhelpful and cannot be relied upon to include the values of properties. (E.g.,
@{ foo = 'bar' } | Select-String foo does not work as intended; it is equivalent to
@{ foo = 'bar' }.ToString() | Select-String foo and therefore to
'System.Collections.Hashtable' | Select-String foo

Arguably, Select-String should always have defaulted to searching through the input objects' formatted string representations:

  • That there is demand for this behavior is evidenced by the fact that PowerShell versions 5 and above (both editions) ship with the oss convenience function, which is a wrapper for Out-String -Stream.

  • GitHub issue #10726 asks that the current behavior of Select-String be changed to search the for-display string representations by default.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Thank you for this clarification. It is amazing helpful. – Ccorock Aug 20 '21 at 18:15
  • 1
    I also learned something, I never knew how or what defined the default display of certain PowerShell objects in the console. `for-display` seems to be the key word here. Also never thought to pipe an object to `Out-String -Stream` to search that same visual representation – codewario Aug 20 '21 at 18:16
  • Glad to hear it, @Ccorock; please also see my update. – mklement0 Aug 20 '21 at 18:21
  • Nice. I wish they'd alias `Select-String` to `ss` out of the box too, but I guess I can kind of see why they'd stay away from that one. – codewario Aug 20 '21 at 18:28
  • 1
    @BendertheGreatest :) `sls` is the built-in alias, though note that its name is irregularly formed: since `sc` is the official alias prefix for `Select-*` commands, it should have been `scs`. – mklement0 Aug 20 '21 at 18:31
2

Okay, you don't want to use Select-String here (although you can, see @mklement0's helpful answer, looking at object properties is usually preferred). That is returning an object and you want to check the Status property for "Completed". Make the following change to the -not subexpression:

(-not (Get-ComplianceSearch -Identity $searchName | Where-Object {
  $_.Status -eq 'Completed'
}))

The above can be on one line but I broke it up for readability.


Basically, Select-String looks for content in strings. If you are looking for a particular value of an object property however, you can use Where-Object to test for a condition and return any objects matching that condition. In this case, we want to return any object that have a Status of 'Completed', so we can negate that in the if statement.


You (or others) might be wondering how this works since Where-Object returns matching objects, but not booleans. The answer is "truthiness". PowerShell objects are "truthy", which means anything can be evaluated as a [bool].

The following values evaluate to $false in most cases. I've included some gotchas to watch out for when relying on "truthy" values:

  • A numeric value of 0
    • A string value of 0 evaluates as $true
  • Empty arrays
  • Empty strings
    • A whitespace-only string or strings consisting only of non-printable characters evaluates as $true
  • $false
    • A string value of False evaluates as $true

Most everything else will evaluate to $true. This is also why comparison operators are syntactically optional when checking whether a variable is $null or not. Although there are times when an explicit value check is a good idea as comparison operators compare the actual values instead of only whether the variable "is" or "isn't".

How does this apply to the expression above then? Simple. if statements, always treat the condition expression as a [bool], no conversion required. In addition, logical operators and conditional operators also imply a boolean comparison. For example, $var = $obj assigns $obj to $var, but
$var = $obj -eq $obj2 or $var = $obj -and $obj2 will assign $true or $false.


So knowing the above, if Where-Object returns nothing, it's $false. If it returns a tangible object, it's $true.

codewario
  • 19,553
  • 20
  • 90
  • 159