Why is an empty PowerShell pipeline not the same as null? - powershell

I am trying to understand the behavior of the #() array constructor, and I came across this very strange test.
It seems that the value of an empty pipeline is "not quite" the same as $null, even though it is -eq $null
The output of each statement is shown after the ###
$y = 1,2,3,4 | ? { $_ -ge 5 }
$z = $null
if ($y -eq $null) {'y is null'} else {'y NOT null'} ### y is null
if ($z -eq $null) {'z is null'} else {'z NOT null'} ### z is null
$ay = #($y)
$az = #($z)
"ay.length = " + $ay.length ### ay.length = 0
"az.length = " + $az.length ### az.length = 1
$az[0].GetType() ### throws exception because $az[0] is null
So the $az array has length one, and $az[0] is $null.
But the real question is: how is it possible that both $y and $z are both -eq $null, and yet when I construct arrays with #(...) then one array is empty, and the other contains a single $null element?

Expanding on Frode F.'s answer, "nothing" is a mostly magical value in PowerShell - it's called [System.Management.Automation.Internal.AutomationNull]::Value. The following will work similarly:
$y = 1,2,3,4 | ? { $_ -ge 5 }
$y = [System.Management.Automation.Internal.AutomationNull]::Value
PowerShell treats the value AutomationNull.Value like $null in most places, but not everywhere. One notable example is in a pipeline:
$null | % { 'saw $null' }
[System.Management.Automation.Internal.AutomationNull]::Value | % { 'saw AutomationNull.Value' }
This will only print:
saw $null
Note that expressions are themselves pipelines even if you don't have a pipeline character, so the following are roughly equivalent:
#($y)
#($y | Write-Output)
Understanding this, it should be clear that if $y holds the value AutomationNull.Value, nothing is written to the pipeline, and hence the array is empty.
One might ask why $null is written to the pipeline. It's a reasonable question. There are some situations where scripts/cmdlets need to indicate "failed" without using exceptions - so "no result" must be different, $null is the obvious value to use for such situations.
I've never run across a scenario where one needs to know if you have "no value" or $null, but if you did, you could use something like this:
function Test-IsAutomationNull
{
param(
[Parameter(ValueFromPipeline)]
$InputObject)
begin
{
if ($PSBoundParameters.ContainsKey('InputObject'))
{
throw "Test-IsAutomationNull only works with piped input"
}
$isAutomationNull = $true
}
process
{
$isAutomationNull = $false
}
end
{
return $isAutomationNull
}
}
dir nosuchfile* | Test-IsAutomationNull
$null | Test-IsAutomationNull

The reason you're experiencing this behaviour is becuase $null is a value. It's a "nothing value", but it's still a value.
PS P:\> $y = 1,2,3,4 | ? { $_ -ge 5 }
PS P:\> Get-Variable y | fl *
#No value survived the where-test, so y was never saved as a variable, just as a "reference"
Name : y
Description :
Value :
Visibility : Public
Module :
ModuleName :
Options : None
Attributes : {}
PS P:\> $z = $null
PS P:\> Get-Variable z | fl *
#Our $null variable is saved as a variable, with a $null value.
PSPath : Microsoft.PowerShell.Core\Variable::z
PSDrive : Variable
PSProvider : Microsoft.PowerShell.Core\Variable
PSIsContainer : False
Name : z
Description :
Value :
Visibility : Public
Module :
ModuleName :
Options : None
Attributes : {}
The way #() works, is that it guarantees that the result is delievered inside a wrapper(an array). This means that as long as you have one or more objects, it will wrap it inside an array(if it's not already in an array like multiple objects would be).
$y is nothing, it's a reference, but no variable data was stored. So there is nothing to create an array with. $z however, IS a stored variable, with nothing(null-object) as the value. Since this object exists, the array constructor can create an array with that one item.

Related

Replace PSObj property value based on a list

I've the following PSObj with some properties stored in an $array :
ComputerName : MyComputer
Time : 08/11/2022 13:57:53
DetectionFile : MyBadFile.exe
ThreatName : WS.Reputation.1
Action : 12
I'm trying to replace the action ID number by it's corresponding description. I've a hashtable with the possibles reasons behind the Action ID
$ActionId = #{
0 = 'Unknown'
1 = 'Blocked'
2 = 'Allowed'
3 = 'No Action'
4 = 'Logged'
5 = 'Command Script Run'
6 = 'Corrected'
7 = 'Partially Corrected'
8 = 'Uncorrected'
10 = 'Delayed Requires reboot to finish the operation.'
11 = 'Deleted'
12 = 'Quarantined'
13 = 'Restored'
14 = 'Detected'
15 = 'Exonerated No longer suspicious (re-scored).'
16 = 'Tagged Marked with extended attributes.'
}
I'm trying to parse each item of this array, and each value of the reason ID to replace the ID by the reason string
# parse array
foreach ($Item in $array) {
# parse possible values
foreach ($value in $ActionId) {
if ($value -eq $item.Action) {
$Item.Action = $ActionId[$value]
$Item.Action
}
}
From my understanding, I'm missing the correct syntax here
$Item.Action = $ActionId[$value]
I do not get any errors, but from the debugger, I'm replacing the action property by $null with the above...
The immediate fix is to loop over the keys (.Keys) of your $ActionId hashtable:
foreach ($Item in $array) {
# parse possible values
foreach ($value in $ActionId.Keys) {
if ($value -eq $item.Action) {
$Item.Action = $ActionId[$value]
$Item.Action # diagnostic output
}
}
}
Note:
To avoid confusion, consider renaming $value to $key.
Generally, note that hashtables are not enumerated in the pipeline / in looping constructs in PowerShell.
That is, foreach ($value in $ActionId) ... doesn't actually loop over the hashtable's entries, and is the same as $value = $ActionID)
If you want to enumerate a hashtable's entries - as key-value pairs of type System.RuntimeType - you would need to use the .GetEnumerator() method; in your case, however, enumerating the keys is sufficient.
However, the simpler and more efficient solution is to test whether the $Item.Action value exists as a key in your hashtable, using the latter's .Contains() method:[1]
foreach ($Item in $array) {
if ($ActionId.Contains($Item.Action)) {
$Item.Action = $ActionId[$Item.Action]
$Item.Action # diagnostic output
}
}
You can further streamline this as follows, though it is conceptually a bit obscure:
foreach ($Item in $array) {
if ($null -ne ($value = $ActionId[$Item.Action])) {
$Item.Action = $value
$Item.Action # diagnostic output
}
}
= is only ever PowerShell's assignment operator; for equality / non-equality comparison, -eq / -ne is required.
Here, an assignment to $value is indeed being performed and the assigned value then acts as the RHS of the -ne operation; in other words: you can use assignment as expressions in PowerShell.
If hashtable $ActionId has no key with value $Item.Action, $ActionId[$Item.Action] quietly returns $null.
Finally - in PowerShell (Core) 7+ only - an even more concise (though not necessarily faster) solution is possible, using ??, the null-coalescing operator:
foreach ($Item in $array) {
$Item.Action = $ActionId[$Item.Action] ?? $Item.Action
$Item.Action # diagnostic output
}
That is, the value of $ActionId[$Item.Action] is only used if it isn't $null; otherwise, $Item.Action, i.e. the current value, is used (which is effectively a no-op).
[1] .ContainsKey() works too, and while this name is conceptually clearer than .Contains(), it is unfortunately not supported by PowerShell's [ordered] hashtables (System.Collections.Specialized.OrderedDictionary) and, generally speaking, not supported by other dictionary (hashtable-like types), given that the System.Collections.IDictionary interface only has .Contains()
In addition mklement0's helpful answer, I was just thinking outside the box (aka question):
This is typical situation where I would consider to use an enum except for the fact that that the keys do not (easily) accept spaces (as in your question).
Enum ActionTypes {
Unknown
Blocked
Allowed
NoAction
Logged
CommandScriptRun
Corrected
PartiallyCorrected
Uncorrected
Delayed
Deleted
Quarantined
Restored
Detected
Exonerated
Tagged
}
$PSObj = [PSCustomObject]#{
ComputerName = 'MyComputer'
Time = [DateTime]'08/11/2022 13:57:53'
DetectionFile = 'MyBadFile.exe'
ThreatName = 'WS.Reputation.1'
Action = 12
}
$PSObj.Action = [ActionTypes]$PSObj.Action
$PSObj
ComputerName : MyComputer
Time : 8/11/2022 1:57:53 PM
DetectionFile : MyBadFile.exe
ThreatName : WS.Reputation.1
Action : Restored
The advantage is that you won't lose the actual action id, meaning if you e.g. insert the object back into a database, it will automatically type cast to the original integer type:
$PSObj.Action
Restored
[int]$PSObj.Action
12

Printing useful variable values in powershell (in particular, involving $null/empty string)

If I have:
$a=$null
$b=''
$c=#($null,$null)
$d='foo'
write-host $a
write-host $b
write-host $c
write-host $d
the output is
foo
I'd really like to be able to easily get output that shows the variable values, e.g.,
$Null
''
#($Null,$Null)
'foo'
I can write a function to do this, but I'm guessing/hoping there's something built-in that I'm missing. Is there, or does everyone just roll their own function for something like this?
At the moment the quickest thing I've come up with is running a value through ConvertTo-Json before printing it. It doesn't handle a plain $null, but it shows me the other values nicely.
What you're looking for is similar to Ruby's .inspect method. It's something I always loved in Ruby and do miss in PowerShell/.Net.
Unfortunately there is no such thing to my knowledge, so you will somewhat have to roll your own.
The closest you get in .Net is the .ToString() method that, at a minimum, just displays the object type (it's inherited from [System.Object]).
So you're going to have to do some checking on your own. Let's talk about the edge case checks.
Arrays
You should check if you're dealing with an array first, because PowerShell often unrolls arrays and coalesces objects for you so if you start doing other checks you may not handle them correctly.
To check that you have an array:
$obj -is [array]
1 -is [array] # false
1,2,3 -is [array] # true
,1 -is [array] #true
In the case of an array, you'll have to iterate it if you want to properly serialize its elements as well. This is basically the part where your function will end up being recursive.
function Format-MyObject {
param(
$obj
)
if ($obj -is [array]) {
# initial array display, like "#(" or "["
foreach ($o in $obj) {
Format-MyObject $obj
}
# closing array display, like ")" or "]"
}
}
Nulls
Simply check if it's equal to $null:
$obj -eq $null
Strings
You can first test that you're dealing with a string by using -is [string].
For empty, you can compare the string to an empty string, or better, to [string]::Empty. You can also use the .IsNullOrEmpty() method, but only if you've already ruled out a null value (or checked that it is indeed a string):
if ($obj -is [string) {
# pick one
if ([string]::IsNullOrEmpty($obj)) {
# display empty string
}
if ($obj -eq [string]::Empty) {
# display empty string
}
if ($obj -eq "") { # this has no advantage over the previous test
# display empty string
}
}
Alternative
You could use the built-in XML serialization, then parse the XML to get the values out of it.
It's work (enough that I'm not going to do it in an SO answer), but it removes a lot of potential human error, and sort of future-proofs the approach.
The basic idea:
$serialized = [System.Management.Automation.PSSerializer]::Serialize($obj) -as [xml]
Now, use the built in XML methods to parse it and pull out what you need. You still need to convert some stuff to other stuff to display the way you want (like interpreting <nil /> and the list of types to properly display arrays and such), but I like leaving the actual serialization to an official component.
Quick example:
[System.Management.Automation.PSSerializer]::Serialize(#(
$null,
1,
'string',
#(
'start of nested array',
$null,
'2 empty strings next',
'',
([string]::Empty)
)
)
)
And the output:
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>System.Object[]</T>
<T>System.Array</T>
<T>System.Object</T>
</TN>
<LST>
<Nil />
<I32>1</I32>
<S>string</S>
<Obj RefId="1">
<TNRef RefId="0" />
<LST>
<S>start of nested array</S>
<Nil />
<S>2 empty strings next</S>
<S></S>
<S></S>
</LST>
</Obj>
</LST>
</Obj>
</Objs>
I shared two functions that reveal PowerShell values (including the empty $Null's, empty arrays etc.) further than the usually do:
One that the serializes the PowerShell objects to a PowerShell
Object Notation (PSON)
which ultimate goal is to be able to reverse everything with the
standard command Invoke-Expression and parse it back to a
PowerShell object.
The other is the ConvertTo-Text (alias CText) function that I used in
my Log-Entry
framework. note the
specific line: Log "Several examples that usually aren't displayed
by Write-Host:" $NotSet #() #(#()) #(#(), #()) #($Null) that I wrote
in the example.
Function Global:ConvertTo-Text1([Alias("Value")]$O, [Int]$Depth = 9, [Switch]$Type, [Switch]$Expand, [Int]$Strip = -1, [String]$Prefix, [Int]$i) {
Function Iterate($Value, [String]$Prefix, [Int]$i = $i + 1) {ConvertTo-Text $Value -Depth:$Depth -Strip:$Strip -Type:$Type -Expand:$Expand -Prefix:$Prefix -i:$i}
$NewLine, $Space = If ($Expand) {"`r`n", ("`t" * $i)} Else{"", ""}
If ($O -eq $Null) {$V = '$Null'} Else {
$V = If ($O -is "Boolean") {"`$$O"}
ElseIf ($O -is "String") {If ($Strip -ge 0) {'"' + (($O -Replace "[\s]+", " ") -Replace "(?<=[\s\S]{$Strip})[\s\S]+", "...") + '"'} Else {"""$O"""}}
ElseIf ($O -is "DateTime") {$O.ToString("yyyy-MM-dd HH:mm:ss")}
ElseIf ($O -is "ValueType" -or ($O.Value.GetTypeCode -and $O.ToString.OverloadDefinitions)) {$O.ToString()}
ElseIf ($O -is "Xml") {(#(Select-XML -XML $O *) -Join "$NewLine$Space") + $NewLine}
ElseIf ($i -gt $Depth) {$Type = $True; "..."}
ElseIf ($O -is "Array") {"#(", #(&{For ($_ = 0; $_ -lt $O.Count; $_++) {Iterate $O[$_]}}), ")"}
ElseIf ($O.GetEnumerator.OverloadDefinitions) {"#{", (#(ForEach($_ in $O.Keys) {Iterate $O.$_ "$_ = "}) -Join "; "), "}"}
ElseIf ($O.PSObject.Properties -and !$O.value.GetTypeCode) {"{", (#(ForEach($_ in $O.PSObject.Properties | Select -Exp Name) {Iterate $O.$_ "$_`: "}) -Join "; "), "}"}
Else {$Type = $True; "?"}}
If ($Type) {$Prefix += "[" + $(Try {$O.GetType()} Catch {$Error.Remove($Error[0]); "$Var.PSTypeNames[0]"}).ToString().Split(".")[-1] + "]"}
"$Space$Prefix" + $(If ($V -is "Array") {$V[0] + $(If ($V[1]) {$NewLine + ($V[1] -Join ", $NewLine") + "$NewLine$Space"} Else {""}) + $V[2]} Else {$V})
}; Set-Alias CText ConvertTo-Text -Scope:Global -Description "Convert value to readable text"
ConvertTo-Text
The ConvertTo-Text function (Alias CText) recursively converts PowerShell object to readable text this includes hash tables, custom objects and revealing type details (like $Null vs an empty string).
Syntax
ConvertTo-Text [<Object>] [[-Depth] <int>] [[-Strip] <int>] <string>] [-Expand] [-Type]
Parameters
<Object>
The object (position 0) that should be converted a readable value.
-⁠Depth <int>
The maximal number of recursive iterations to reveal embedded objects.
The default depth for ConvertTo-Text is 9.
-Strip <int>
Truncates strings at the given length and removes redundant white space characters if the value supplied is equal or larger than 0. Set -Strip -1 prevents truncating and the removal of with space characters.
The default value for ConvertTo-Text is -1.
-Expand
Expands embedded objects over multiple lines for better readability.
-Type
Explicitly reveals the type of the object by adding [<Type>] in front of the objects.
Note: the parameter $Prefix is for internal use.
Examples
The following command returns a string that describes the object contained by the $var variable:
ConvertTo-Text $Var
The following command returns a string containing the hash table as shown in the example (rather then System.Collections.DictionaryEntry...):
ConvertTo-Text #{one = 1; two = 2; three = 3}
The following command reveals values (as e.g. $Null) that are usually not displayed by PowerShell:
ConvertTo-Text #{Null = $Null; EmptyString = ""; EmptyArray = #(); ArrayWithNull = #($Null); DoubleEmptyArray = #(#(), #())} -Expand
The following command returns a string revealing the WinNT User object up to a level of 5 deep and expands the embedded object over multiple lines:
ConvertTo-Text ([ADSI]"WinNT://./$Env:Username") -Depth 5 -Expand
A quick self-rolled option good for some datatypes.
function Format-MyObject {
param(
$obj
)
#equality comparison order is important due to array -eq overloading
if ($null -eq $obj)
{
return 'null'
}
#Specify depth because the default is 2, because powershell
return ConvertTo-Json -Depth 100 $obj
}

Ternary operator in PowerShell

From what I know, PowerShell doesn't seem to have a built-in expression for the so-called ternary operator.
For example, in the C language, which supports the ternary operator, I could write something like:
<condition> ? <condition-is-true> : <condition-is-false>;
If that doesn't really exist in PowerShell, what would be the best way (i.e. easy to read and to maintain) to accomplish the same result?
$result = If ($condition) {"true"} Else {"false"}
For use in or as an expression, not just an assignment, wrap it in $(), thus:
write-host $(If ($condition) {"true"} Else {"false"})
Powershell 7 has it.
PS C:\Users\js> 0 ? 'yes' : 'no'
no
PS C:\Users\js> 1 ? 'yes' : 'no'
yes
The closest PowerShell construct I've been able to come up with to emulate that is:
#({'condition is false'},{'condition is true'})[$condition]
Try powershell's switch statement as an alternative, especially for variable assignment - multiple lines, but readable.
Example,
$WinVer = switch ( Test-Path -Path "$Env:windir\SysWOW64" ) {
$true { "64-bit" }
$false { "32-bit" }
}
"This version of Windows is $WinVer"
Per this PowerShell blog post, you can create an alias to define a ?: operator:
set-alias ?: Invoke-Ternary -Option AllScope -Description "PSCX filter alias"
filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock]$ifFalse)
{
if (&$decider) {
&$ifTrue
} else {
&$ifFalse
}
}
Use it like this:
$total = ($quantity * $price ) * (?: {$quantity -le 10} {.9} {.75})
As of PowerShell version 7, the ternary operator is built into PowerShell.
1 -gt 2 ? "Yes" : "No"
# Returns "No"
1 -gt 2 ? 'Yes' : $null
# Get a $null response for false-y return value
I too, looked for a better answer, and while the solution in Edward's post is "ok", I came up with a far more natural solution in this blog post
Short and sweet:
# ---------------------------------------------------------------------------
# Name: Invoke-Assignment
# Alias: =
# Author: Garrett Serack (#FearTheCowboy)
# Desc: Enables expressions like the C# operators:
# Ternary:
# <condition> ? <trueresult> : <falseresult>
# e.g.
# status = (age > 50) ? "old" : "young";
# Null-Coalescing
# <value> ?? <value-if-value-is-null>
# e.g.
# name = GetName() ?? "No Name";
#
# Ternary Usage:
# $status == ($age > 50) ? "old" : "young"
#
# Null Coalescing Usage:
# $name = (get-name) ? "No Name"
# ---------------------------------------------------------------------------
# returns the evaluated value of the parameter passed in,
# executing it, if it is a scriptblock
function eval($item) {
if( $item -ne $null ) {
if( $item -is "ScriptBlock" ) {
return & $item
}
return $item
}
return $null
}
# an extended assignment function; implements logic for Ternarys and Null-Coalescing expressions
function Invoke-Assignment {
if( $args ) {
# ternary
if ($p = [array]::IndexOf($args,'?' )+1) {
if (eval($args[0])) {
return eval($args[$p])
}
return eval($args[([array]::IndexOf($args,':',$p))+1])
}
# null-coalescing
if ($p = ([array]::IndexOf($args,'??',$p)+1)) {
if ($result = eval($args[0])) {
return $result
}
return eval($args[$p])
}
# neither ternary or null-coalescing, just a value
return eval($args[0])
}
return $null
}
# alias the function to the equals sign (which doesn't impede the normal use of = )
set-alias = Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment."
Which makes it easy to do stuff like (more examples in blog post):
$message == ($age > 50) ? "Old Man" :"Young Dude"
Since a ternary operator is usually used when assigning value, it should return a value. This is the way that can work:
$var=#("value if false","value if true")[[byte](condition)]
Stupid, but working. Also this construction can be used to quickly turn an int into another value, just add array elements and specify an expression that returns 0-based non-negative values.
The ternary operator in PowerShell was introduced with the PowerShell version7.0.
[Condition] ? (output if True) : (output if False)
Example 01
$a = 5; $b = 6
($a -gt $b) ? "True" : "False"
Output
False
Example 02
($a -gt $b) ? ("$a is greater than $b") : ("$a is less than $b")
Output
5 is less than 6
more information
https://www.tutorialspoint.com/how-ternary-operator-in-powershell-works
Since I have used this many times already and didn't see it listed here, I'll add my piece :
$var = #{$true="this is true";$false="this is false"}[1 -eq 1]
ugliest of all !
kinda source
I've recently improved (open PullRequest) the ternary conditional and null-coalescing operators in the PoweShell lib 'Pscx'
Pls have a look for my solution.
My github topic branch: UtilityModule_Invoke-Operators
Functions:
Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe
Aliases
Set-Alias :?: Pscx\Invoke-Ternary -Description "PSCX alias"
Set-Alias ?: Pscx\Invoke-TernaryAsPipe -Description "PSCX alias"
Set-Alias :?? Pscx\Invoke-NullCoalescing -Description "PSCX alias"
Set-Alias ?? Pscx\Invoke-NullCoalescingAsPipe -Description "PSCX alias"
Usage
<condition_expression> |?: <true_expression> <false_expression>
<variable_expression> |?? <alternate_expression>
As expression you can pass:
$null, a literal, a variable, an 'external' expression ($b -eq 4) or a scriptblock {$b -eq 4}
If a variable in the variable expression is $null or not existing, the alternate expression is evaluated as output.
PowerShell currently doesn't didn't have a native Inline If (or ternary If) but you could consider to use the custom cmdlet:
IIf <condition> <condition-is-true> <condition-is-false>
See: PowerShell inline If (IIf)
If you're just looking for a syntactically simple way to assign/return a string or numeric based on a boolean condition, you can use the multiplication operator like this:
"Condition is "+("true"*$condition)+("false"*!$condition)
(12.34*$condition)+(56.78*!$condition)
If you're only ever interested in the result when something is true, you can just omit the false part entirely (or vice versa), e.g. a simple scoring system:
$isTall = $true
$isDark = $false
$isHandsome = $true
$score = (2*$isTall)+(4*$isDark)+(10*$isHandsome)
"Score = $score"
# or
# "Score = $((2*$isTall)+(4*$isDark)+(10*$isHandsome))"
Note that the boolean value should not be the leading term in the multiplication, i.e. $condition*"true" etc. won't work.
Here's an alternative custom function approach:
function Test-TernaryOperatorCondition {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[bool]$ConditionResult
,
[Parameter(Mandatory = $true, Position = 0)]
[PSObject]$ValueIfTrue
,
[Parameter(Mandatory = $true, Position = 1)]
[ValidateSet(':')]
[char]$Colon
,
[Parameter(Mandatory = $true, Position = 2)]
[PSObject]$ValueIfFalse
)
process {
if ($ConditionResult) {
$ValueIfTrue
}
else {
$ValueIfFalse
}
}
}
set-alias -Name '???' -Value 'Test-TernaryOperatorCondition'
Example
1 -eq 1 |??? 'match' : 'nomatch'
1 -eq 2 |??? 'match' : 'nomatch'
Differences Explained
Why is it 3 question marks instead of 1?
The ? character is already an alias for Where-Object.
?? is used in other languages as a null coalescing operator, and I wanted to avoid confusion.
Why do we need the pipe before the command?
Since I'm utilising the pipeline to evaluate this, we still need this character to pipe the condition into our function
What happens if I pass in an array?
We get a result for each value; i.e. -2..2 |??? 'match' : 'nomatch' gives: match, match, nomatch, match, match (i.e. since any non-zero int evaluates to true; whilst zero evaluates to false).
If you don't want that, convert the array to a bool; ([bool](-2..2)) |??? 'match' : 'nomatch' (or simply: [bool](-2..2) |??? 'match' : 'nomatch')

How do I assign a null value to a variable in PowerShell?

I want to assign a null value to a variable called $dec, but it gives me errors. Here is my code:
import-module activedirectory
$domain = "domain.example.com"
$dec = null
Get-ADComputer -Filter {Description -eq $dec}
These are automatic variables, like $null, $true, $false etc.
about_Automatic_Variables, see https://technet.microsoft.com/en-us/library/hh847768.aspx?f=255&MSPPError=-2147217396
$NULL
$null is an automatic variable that contains a NULL or empty
value. You can use this variable to represent an absent or undefined
value in commands and scripts.
Windows PowerShell treats $null as an object with a value, that is, as
an explicit placeholder, so you can use $null to represent an empty
value in a series of values.
For example, when $null is included in a collection, it is counted as
one of the objects.
C:\PS> $a = ".dir", $null, ".pdf"
C:\PS> $a.count
3
If you pipe the $null variable to the ForEach-Object cmdlet, it
generates a value for $null, just as it does for the other objects.
PS C:\ps-test> ".dir", $null, ".pdf" | Foreach {"Hello"}
Hello
Hello
Hello
As a result, you cannot use $null to mean "no parameter value." A
parameter value of $null overrides the default parameter value.
However, because Windows PowerShell treats the $null variable as a
placeholder, you can use it scripts like the following one, which
would not work if $null were ignored.
$calendar = #($null, $null, “Meeting”, $null, $null, “Team Lunch”, $null)
$days = Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"
$currentDay = 0
foreach($day in $calendar)
{
if($day –ne $null)
{
"Appointment on $($days[$currentDay]): $day"
}
$currentDay++
}
output:
Appointment on Tuesday: Meeting
Appointment on Friday: Team lunch
Use $dec = $null
From the documentation:
$null is an automatic variable that contains a NULL or empty value. You can use this variable to represent an absent or undefined value in commands and scripts.
PowerShell treats $null as an object with a value, that is, as an explicit placeholder, so you can use $null to represent an empty value in a series of values.
If the goal simply is to list all computer objects with an empty description attribute try this
import-module activedirectory
$domain = "domain.example.com"
Get-ADComputer -Filter '*' -Properties Description | where { $_.Description -eq $null }
As others have said, use $null.
However, the handling of $null is not so simple.
In lists (or, more precisely, System.Array objects) $null is treated as a placeholding object when indexing the list, so ($null, $null).count outputs 2.
But otherwise $null is treated as a flag signifying that there is no content (no object; or, more precisely, a "null-valued expression", as reported by .GetType()), so ($null).count outputs 0.
Thus
$null.count; # Output = 0
($null).count; # Output = 0
(, $null).count; # Output = 1
($null, $null).count; # Output = 2
($null, $null, $null).count; # Output = 3
Note: the same output is returned from .count and .length in the above context.
Similarly if explicitly assigning any of the above to a variable, as in
$aaa = $null; $aaa.count
$bbb = ($null, $null); $bbb.count
which output, respectively, 0 and 2.
Similarly if looping with ForEach, as in
$aaa = $null; ForEach ($a in $aaa) {write-host "Foo" -NoNewLine}
$bbb = ($null, $null); ForEach ($b in $bbb) {write-host "Bar" -NoNewLine}
which output, respectively, nothing and BarBar.
However, note well that when operating on an individual item that has been returned from a list $null is again treated as a "null-valued expression", as can be confirmed by running
$xxx = ($null, "foo", $null); ForEach ($x in $xxx) {write-host "C=" $x.count "| " -NoNewLine}
which outputs C= 0 | C= 1 | C= 0 | .

How do I loop through a dataset in PowerShell?

I am trying to output values of each rows from a DataSet:
for ($i=0;$i -le $ds.Tables[1].Rows.Count;$i++)
{
Write-Host 'value is : ' + $i + ' ' + $ds.Tables[1].Rows[$i][0]
}
gives the output ...
value is : +0+ +System.Data.DataSet.Tables[1].Rows[0][0]
value is : +1+ +System.Data.DataSet.Tables[1].Rows[1][0]
value is : +2+ +System.Data.DataSet.Tables[1].Rows[2][0]
value is : +3+ +System.Data.DataSet.Tables[1].Rows[3][0]
value is : +4+ +System.Data.DataSet.Tables[1].Rows[4][0]
value is : +5+ +System.Data.DataSet.Tables[1].Rows[5][0]
value is : +6+ +System.Data.DataSet.Tables[1].Rows[6][0]
How do I get the actual value from the column?
The PowerShell string evaluation is calling ToString() on the DataSet. In order to evaluate any properties (or method calls), you have to force evaluation by enclosing the expression in $()
for($i=0;$i -lt $ds.Tables[1].Rows.Count;$i++)
{
write-host "value is : $i $($ds.Tables[1].Rows[$i][0])"
}
Additionally foreach allows you to iterate through a collection or array without needing to figure out the length.
Rewritten (and edited for compile) -
foreach ($Row in $ds.Tables[1].Rows)
{
write-host "value is : $($Row[0])"
}
Here's a practical example (build a dataset from your current location):
$ds = new-object System.Data.DataSet
$ds.Tables.Add("tblTest")
[void]$ds.Tables["tblTest"].Columns.Add("Name",[string])
[void]$ds.Tables["tblTest"].Columns.Add("Path",[string])
dir | foreach {
$dr = $ds.Tables["tblTest"].NewRow()
$dr["Name"] = $_.name
$dr["Path"] = $_.fullname
$ds.Tables["tblTest"].Rows.Add($dr)
}
$ds.Tables["tblTest"]
$ds.Tables["tblTest"] is an object that you can manipulate just like any other Powershell object:
$ds.Tables["tblTest"] | foreach {
write-host 'Name value is : $_.name
write-host 'Path value is : $_.path
}
The parser is having trouble concatenating your string. Try this:
write-host 'value is : '$i' '$($ds.Tables[1].Rows[$i][0])
Edit: Using double quotes might also be clearer since you can include the expressions within the quoted string:
write-host "value is : $i $($ds.Tables[1].Rows[$i][0])"