I am trying to compare two objects and I can see both objects have values (and they are the same), but running the compare results in error referenceobject is null. See screenshot
Not sure where to go with this.
I would make a quick check to see if any value is equal to null and then do a comparison if values are found. Even if you can see both objects have values, your ps command may not.
The following accepts null values:
Compare-Object -ReferenceObject #($Value1 | Select-Object) -DifferenceObject #($Value2 | Select-Object)
Related
I have two variables both which contain the Get-Mailbox object "Identity" of user accounts. I need to subtract the contents of one from the other IE:
$termednofwd = (domain.local/OUname/SubOU/Users/first1 last1, domain.local/OUname/SubOU/first2 last2)
$termedfmr = (domain.local/OUname/SubOU/Users/first1 last1)
I want something that would subtract the contents of $termedfmr from $termednofwd giving something like the below. Compare-Object only lists the contents that are in both, I basically need to subtract what is in both from the first variable.
essentially:
$termednofwdnofmr = $termednofwd - $termedfmr resulting in this:
$termednofwdnofmr = (domain.local/OUname/SubOU/first2 last2)
In set theory terms, you're looking for the relative complement between two collections, which Compare-Object can provide, although it requires additional effort:
By default, Compare-Object provides the symmetric difference between two sets, i.e. it lists the union of relative complements; that is, given two sets A and B, it lists both those elements of B not present in A and those elements of A not present in B, and it uses the .SideIndicator property to indicate which is which:
'<=' indicates objects unique to set A (-ReferenceObject argument, or first positional argument), whereas
'=>' indicates elements unique to set B (-DifferenceObject argument, or second positional argument).
Therefore, you need to filter the output objects by their .SideIndicator values.
The -PassThru switch additionally ensures that the input objects are passed through (as opposed to wrapping them in a [pscustomobject] instance whose .InputObject contains them):
$termednofwd = 'domain.local/OUname/SubOU/Users/first1 last1',
'domain.local/OUname/SubOU/first2 last2'
$termedfmr = 'domain.local/OUname/SubOU/Users/first1 last1'
# Return the elements in $termednofwd that aren't also present in $termedfmr
Compare-Object $termednofwd $termedfmr -PassThru |
Where-Object SideIndicator -eq '<='
The above yields 'domain.local/OUname/SubOU/first2 last2', i.e. those element(s) in $termednofwd that aren't also present in $termedfmr.
Note: The above uses strings as input objects for brevity; in your case, since you're working with the objects returned by the Get-Mailbox cmdlet and want to compare based on their .Identity property values, you need to use:
# If you only need the identity values as results.
Compare-Object $termednofwd.Identity $termedfmr.Identity -PassThru |
Where-Object SideIndicator -eq '<='
# Alternatively, if you need the whole mailbox objects.
Compare-Object -Property Identity $termednofwd $termedfmr -PassThru |
Where-Object SideIndicator -eq '<='
See also: GitHub issue #4316, which proposes enhancing Compare-Object with set operations.
So this is essentially a practical example of Set theory i.e. I'd like all the items from Set 1 that isn't in Set 2. PowerShell doesn't have direct commandlets to do that but through the power of .Net you can lervage the Microsoft Linq libraries to do this work for you.
The only trick is that you need to cast your PowerShell variables to arrays of Objects to make the function call work right:
$results = [System.Linq.Enumerable]::Except([object[]]$mainArray, [object[]]$subArray)
Lastly, the items you are comparing in the two arrays have to be comparable. For simple things like strings this is always works. If the things you are comparing are more complex objects, they may not be comparable directly.
I have a requirement where I need to compare JSON object from a file to the JSON message which comes into Anypoint MQ queue. I am able to get the message from the queue. I have used below script but it is not working. I did both -eq and Compare-Object but they are not working.
$po_ps_output = $filemessagecontent | ConvertFrom-Json
$po_python_output = $mqmessagecontent.body | ConvertFrom-Json
$result = $po_ps_output -eq $po_python_output
If you just want to know if the two JSON-originated objects differ, without needing to know how:
$contentEqual = ($po_ps_output | ConvertTo-Json -Compress) -eq
($po_python_output | ConvertTo-Json -Compress)
Note:
ConvertTo-Json defaults to a serialization depth of 2 - use -Depth <n> if your data is more deeply nested to avoid truncation (potentiall data loss) - see this post.
Converting back to JSON may seem like an unnecessary step, but the -Compress standardizes the output formatting to a single line with no extra whitespace, which ensures that incidental variations in formatting in the input (if you had used the input JSON text directly) are ignored.
If you want to know how the two JSON-originated objects differ:
Note: The following is only useful in the following, limited scenario -
a generic, robust solution would require much more effort:
The inputs have the same structure and differ only in property names / values.
The ordering of (equivalent) properties is the same.
Compare-Object (($po_ps_output | ConvertTo-Json) -split '\r?\n') `
(($po_python_output | ConvertTo-Json) -split '\r?\n')
The output will show the lines that differ, each representing a single property or primitive value; e.g.:
InputObject SideIndicator
----------- -------------
"DOB": "12-03-1994" =>
"DOB": "12-03-1999" <=
Note:
=> / <= indicate that the line is unique to the RHS / LHS.
Again, the explicit reconversion to JSON is done to ensure uniform formatting; in this case, a line-oriented pretty-printed format that enables property-by-property comparison.
Again, you may have to use -Depth to prevent truncation of data.
For interactive inspection of differences, you can try a difference-visualization tool, such as the one built into Visual Studio Code, by passing the two JSON strings in pretty-printed form via files to code --diff <file1> <file2>.
As for what you tried:
ConvertFrom-Json creates [pscustomobject] instances, so you're comparing two instances of that type:
If you use -eq, reference equality is tested for, because [pscustomobject] is a reference type and doesn't implement custom equality comparison.
Therefore, $po_ps_output -eq $po_python_output will only be $true if the two variables point to the very same object in memory - which is clearly not the case here, so you'll always get $false.
If you use Compare-Object, the two instances are compared by their .ToString() values.
As of PowerShell Core 7.0.0-preview.4, regrettably, calling .ToString() on an instance of [pscustomobject] yields the empty string (''), which should be considered a bug - see this GitHub issue.
Therefore, Compare-Object $po_ps_output $po_python_output (unhelpfully) considers the two instances equal and returns nothing, since equal objects are by default not output (use -IncludeEqual to include them).
The following two commands produce different output.
Get-ChildItem | Sort-Object -Property Length
Get-ChildItem | Sort-Object -Property Len
Len is not a member of System.IO.FileInfo. Is PowerShell matching Len to the Length member? If not, then why is there no error message saying that Len is not a property?
No, its not member of System.IO.FileInfo as you can see by adding the -Debugswitch:
Get-ChildItem | Sort-Object -Property Len -Debug
Output looks like:
DEBUG: "Sort-Object" - "Len" cannot be found in "InputObject".
I guess the reason for that is the defensive implementation of the cmdlet:
If an object does not have one of the specified properties, the
property value for that object is interpreted by the cmdlet as Null
and is placed at the end of the sort order.
To complement Martin Brandl's helpful answer with more general information:
While PowerShell's elastic syntax only applies to parameter names (e.g., specifying just -p for -Property) , not values (arguments), you do have options for completing values:
At edit time: use tab completion:
This works on the command line as well as in Visual Studio Code with the PowerShell extension installed (where you'll get IntelliSense as well), as long as PowerShell can statically infer the output type(s)[1]
of the command in the previous pipeline segment.
At runtime:
Sort-Object and several other cmdlets allow you to use a wildcard expression to match property names:
Get-ChildItem | Sort-Object -Property Len* # "Len*" matches "Length"
Note that multiple properties may match, and that a given parameter must be explicitly designed to support wildcards (unlike in POSIX-like shells, it is not PowerShell itself that resolves the wildcards).
When accessing a nonexistent property on an object directly, no error is reported by default, and $null is returned:
(Get-Item /).Foo # Outputs $null as the value of nonexistent property "Foo"
By contrast, if Set-StrictMode -Version 2 or higher is in effect, a (statement-terminating) error is reported in that case, but note that Set-StrictMode does not apply when passing property names as arguments, such as to Sort-Object above.
As for a possible motivation for why Sort-Object doesn't enforce the existence of specified properties:
PowerShell allows you to pass objects that are any mix of types as input through the pipeline, with the objects getting passed one at a time.
(Similarly, PowerShell's default array type is [object[]], which allows you to create mixed-type arrays such as 1, 'hi', $True)
Even with (potentially) homogeneous input (such as the [System.IO.FileInfo] instances emitted by Get-ChildItem -File, for instance), a receiving command cannot detect that case up front, because it only ever sees one object at a time.
In general, cmdlets should be able to handle a mix of types among the input gracefully, and treating nonexistent properties as $null is overall the better choice, especially given that:
a cmdlet may still be able to act meaningfully on the input if at least a subset of the input objects have the property of interest (see below).
a cmdlet cannot know in advance whether that subset is empty.
Example with heterogeneous input:
Send an array of custom objects through the pipeline and sort it by property val, which one of the objects lacks:
[pscustomobject] #{ n = 'o1'; val = 2 },
[pscustomobject] #{ n = 'o2' },
[pscustomobject] #{ n = 'o3'; val = 1 } | Sort-Object val
Output:
n val
- ---
o3 1
o1 2
o2
Sorting was performed among all the input objects that do have a .val property, whereas those that don't were placed at the end, as stated in the quote from Sort-Object's documentation in Martin's answer.
[1] This should be true of all built-in cmdlets; to ensure that it works with custom functions, define them with [OutputType(<type>)] attributes - see this answer of mine for more.
In my Powershell script two variables get value through sql query, even though there input value seems to be same the if condition is returning false instead of true.
If Equal condition doesn't work with System-Objects, what condition can I use to check if they are equal or not?
Below is Powershell script part:
Echo "first_result:$first_result,second_result:$second_result"
Echo ""
if($first_result -eq $second_result){
Echo "True"
}
else{
Echo "False"
}
Echo ""
$first_result.getType()
$second_result.getType()
Output:
I have tried other comparison operators -like , -match. Even tried Trim() function, but nothing worked.
I found this topic of java bit similar but didn't get much help for powershell:
Why .equals method is failing on two same value objects?
You are trying to compare two arrays. The equality operator -eq works just fine, but it does a reference comparison while you are looking for a value comparison, i.e. do the contents of the two arrays match? The easiest way to do that is with Compare-Object, which returns an array of differences between the two arrays. If there are no differences, then it returns $null. So you just need to examine the length of the result to check for value equality:
$arraysMatch = #(Compare-Object $first_result $second_result).Length -eq 0
Note that Compare-Object by default compares its arguments in an order-independent fashion, thus 1,3,2 will match 1,2,3. If you want to compare them taking order into account, add the SyncWindow parameter:
$arraysMatch = #(Compare-Object $first_result $second_result -SyncWindow 0).Length -eq 0
-eq is similar to == operator which will compare the address for Objects not values. You must use Array or other collection utilities to compare two Objects.
I wrote a script to find GID's in AD, this is working fine, the issue I'm having is filtering out the blank (null lines)
$searcher=[adsisearcher]"(objectCategory=user)"
$result = $searcher.FindAll()
$result | Select-Object #{Name="DN";Expression+{$_.properties.distinguishedname}},#{Name="gid";Expression={$_.properties.gidnumber }} |Sort-Object -Property gid
I find it odd that you would be getting blank lines with that code. Can't think of a scenario where the distinguishedname of a user is null. The one issue with your code that I do see might just be a typo in your first calculated expression:
#{Name="DN";Expression+{$_.properties.distinguishedname}}
should instead be
#{Name="DN";Expression={$_.properties.distinguishedname}}
However that should have just made a syntax error that would be easily caught before execution.
Filtering Blanks
A real easy PowerShell way to deal with this is to use a simple Where-Object clause. For argument sake lets say that the GID could be empty/null.
$result | Select-Object #{Name="DN";Expression={$_.properties.distinguishedname}},#{Name="gid";Expression={$_.properties.gidnumber }} | Where-Object{$_.GID} | Sort-Object -Property gid
A null or empty value evaluates to False in PowerShell. Where-Object{$_.GID} will only allow objects with a populated property for GID to pass as output. You will get similar results from string static methods as well. These would also add readability to your code.
... | Where-Object{[string]::IsNullOrWhiteSpace($_.GID)} | ...
There is also [string]::IsNullOrEmpty()
Sort Object has a -unique property which will remove most of the blank lines, all but one as you have guessed. You could also pipe to a
where-object -ne '`n'
or something similar, let me know if I should elaborate that.