oddity using newtonsoft json.net with powershell - powershell

I have
function Foo($a, $b)
{
$o = #{}
$o.A = $a
$o.B = $b
$post = #{}
$post.X="x"
$post.entity =$o
$newton::SerializeObject($post)
}
then do
foo "a" "b"
I get
Exception calling "SerializeObject" with "1" argument(s): "Self referencing loop detected for property 'Value' with type 'System.Management.Automation.PSParameterizedProperty'. Path 'entity.Members[0]'."
however
function Foo2($o)
{
$post = #{}
$post.X="x"
$post.entity =$o
$newton::SerializeObject($post)
}
foo2 #{a="a"; b="b"}
works fine. Also
function foo3($a, $b)
{
$o = #{}
$o.A = $a
$o.B = $b
$newton::SerializeObject($o)
}
foo3 "a" "b"
works but
foo3 "a" 1
fails
The latter can be made to work by doing
$o.B= [Int32]::Parse($b.Tostring())
Which all seems very odd
powershell v2 on windows 7, json.net 4.4.5

The JavaScriptSerializer from the .NET framework also has a similar problem with serializing PowerShell's hashes. I suspect it something slightly odd in the PowerShell type system. You could skip Json.Net altogether and roll your own.
Below is something to start you off. It's likely not as robust as PowerShell 3's built-in ConvertTo-Json cmdlet, but I think it's mostly complete.
Here are all of your examples, in working order.
# See below for ConvertTo-Json.psm1
Import-Module ConvertTo-Json
function Foo($a, $b)
{
$o = #{}
$o.A = $a
$o.B = $b
$post = #{}
$post.X="x"
$post.entity =$o
ConvertTo-Json $post
}
function Foo2($o)
{
$post = #{}
$post.X="x"
$post.entity =$o
ConvertTo-Json $post
}
function foo3($a, $b)
{
$o = #{}
$o.A = $a
$o.B = $b
ConvertTo-Json $o
}
PS> foo "a" "b"
{"entity":{"A":"a","B":"b"},"X":"x"}
PS> foo2 #{a="a"; b="b"}
{"entity":{"a":"a","b":"b"},"X":"x"}
PS> foo3 "a" "b"
{"A":"a","B":"b"}
PS> foo3 "a" 1
{"A":"a","B":1}
And here's the PowerShell module that implements ConvertTo-Json.
# Save these contents to Modules\ConvertTo-Json\ConvertTo-Json.psm1 in your
# PowerShell documents folder, and load them in your $profile using the
# "Import-Module ConvertTo-Json" cmdlet. This will make the ConvertTo-Json cmdlet
# available for use.
Set-StrictMode -Version Latest
function convertToJsonNull($InputObject) {
"null"
}
function convertToJsonArray($InputObject) {
$value = ($InputObject | %{ convertToJson $_ }) -join ','
"[$value]"
}
function convertToJsonHash($InputObject) {
$value = ($InputObject.Keys | %{
$name = $_ | asJsonString
$itemValue = convertToJson ($InputObject[$_])
'"{0}":{1}' -f $name, $itemValue
}) -join ','
"{$value}"
}
function convertToJsonObject($InputObject) {
$value = ($InputObject | get-member -membertype *property | %{
$name = $_.Name
$value = convertToJson ($InputObject.($name))
'"{0}":{1}' -f ($name | asJsonString), $value
}) -join ','
"{$value}"
}
function convertToJsonString($InputObject) {
'"{0}"' -f ($InputObject | asJsonString)
}
function convertToJsonBool($InputObject) {
$InputObject.ToString().ToLower()
}
function convertToJsonNumeric($InputObject) {
"$InputObject"
}
function convertToJsonDate($InputObject) {
$epoch = [datetime]"1970-01-01T00:00:00Z"
$elapsed = [long]($InputObject - $epoch).TotalMilliseconds
'"\/Date({0})\/"' -f $elapsed
}
filter isNumeric() {
$_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] -or
$_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] -or
$_ -is [float] -or $_ -is [double] -or $_ -is [decimal]
}
filter asJsonString {
($_ -replace '\\', '\\') -replace '"', '\"'
}
function convertToJson($InputObject) {
if ($InputObject -eq $null) { convertToJsonNull $InputObject }
elseif ($InputObject -is [array]) { convertToJsonArray $InputObject }
elseif ($InputObject -is [hashtable]) { convertToJsonHash $InputObject }
elseif ($InputObject -is [datetime]) { convertToJsonDate $InputObject }
elseif ($InputObject -is [string]) { convertToJsonString $InputObject }
elseif ($InputObject -is [char]) { convertToJsonString $InputObject }
elseif ($InputObject -is [bool]) { convertToJsonBool $InputObject }
elseif ($InputObject | isNumeric) { convertToJsonNumeric $InputObject }
else { convertToJsonObject $InputObject }
}
function ConvertTo-Json {
[CmdletBinding()]
param(
[Parameter(
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true
)]
$InputObject
)
convertToJson $InputObject
}
Export-ModuleMember -Function ConvertTo-Json

The self referencing loop issue sems to be all about.... the order in which you assign things. The below example works:
function Foo($a, $b)
{
$o = #{}
$post = #{}
$post.entity =$o
$o.A = $a
$o.B = $b
$post.X="x"
[Newtonsoft.Json.JsonConvert]::SerializeObject($post)
}
Foo "a" "b"
{"entity":{"A":"a","B":"b"},"X":"x"}
If you convert the type before you pass it in then it will keep your foo3 function generic:
function foo3($a, $b)
{
$o = #{}
$o.A = $a
$o.B = $b
[Newtonsoft.Json.JsonConvert]::SerializeObject($o)
}
$var2 = [Int32]::Parse((1).Tostring())
Foo3 "a" $var2
{"A":"a","B":1}

Related

Powershell Json Array

In relation to this question that was answered, we now have an issue where some values are arrays and we need to retain that structure
The code as it stands is:
function Get-Node {
[CmdletBinding()][OutputType([Object[]])] param(
[ScriptBlock]$Where,
[AllowNull()]
[Parameter(ValueFromPipeLine = $True, Mandatory = $True)]$InputObject,
[Int]$Depth = 90
)
process {
if ($_ -isnot [String] -and $Depth -gt 0) {
if ($_ -is [Collections.IDictionary]) {
if (& $Where) { $_ }
$_.get_Values() | Get-Node -Where $Where -Depth ($Depth - 1)
}
elseif ($_ -is [Collections.IEnumerable]) {
for ($i = 0; $i -lt $_.get_Count(); $i++) {
$_[$i] | Get-Node -Where $Where -Depth ($Depth - 1)
}
}
elseif ($Nodes = $_.PSObject.Properties.Where{ $_.MemberType -eq 'NoteProperty' }) {
$Nodes.ForEach{
if (& $Where) { $_ }
$_.Value | Get-Node -Where $Where -Depth ($Depth - 1)
Write-Host "In Second If " $_.Value
Write-Host "In Second If 2" $_
}
}
}
}
}
And the above function is called via:
if ($content.properties.policyRule -ne $null){
foreach ($rule in $policyRule){
$Node = $content.properties | Get-Node -Where {$_.name -eq $rule -and $_.value -Match "^\[\w+"}
$Node | ForEach-Object {
$_.Value = '[' + $_.Value
}
}
}
And what we are seeing is this:
"notIn": "[[concat(subscription().id,'/')] [subscription().id] /"
What we ideally want:
"notIn": ["[[concat(subscription().id,'/')]", "[[subscription().id]", "/"]
Any suggestions would be grateful

Is there a way to speed up dynamic member look-up in ps-objects

In the following code, most time is spent in $v = $proc.$columnName and I am wondering if there
is a way to speed up looking up the objects's members's values.
In the code below, I have chosen $objs to be the result of get-process but in my case, $objs could be an array of any type of objects, thus the need to look up the objects's members dynamically.
$objs= get-process
$columnNames = #()
foreach ($member in ($objs | get-member -memberType property, noteproperty)) {
[string]$name = $member.name
$columnNames += $name
}
[Int64 ] $sum = 0
[string] $columnName = ''
foreach ($obj in $objs) {
foreach ($columnName in $columnNames) {
$v = $obj.$columnName
# $v = $obj.psObject.members.Item($columnName).value
if ($v -eq $null) {
}
elseif ($v -is [System.IntPtr]) {
$sum = $sum + ($v -as [int64] )
}
elseif ($v -is [System.Int64] -or $v -is [System.Int32]) {
$sum = $sum + $v
}
}
}
"sum = $sum"
Perhaps there are more ways to speed this up, but below I have taken out the unnecessary bits:
$objs= Get-Process
$columnNames = ($objs | Get-Member -MemberType property, noteproperty).Name
[Int64 ] $sum = 0
foreach ($obj in $objs) {
foreach ($v in $columnNames) {
if ($obj.$v -as [int64]) { $sum += [int64]$obj.$v }
}
}
"sum = $sum"
This is a hidden property on PSObjects called .PSObject that way too many people don't know about, including myself until a few weeks ago despite spending thousands of hours working with PowerShell. From there you can use .where to SIGNIFICANTLY increase filtering performance.
New Code Block
New-Variable -Force -Name:'Processes' -value:(Get-Process)
New-Variable -Force -Name:'Sum' -Value:([Int64]$Null)
ForEach ($Process in $Processes) {
$Process.PSObject.Properties.Where({
($_.MemberType -in #('Property','NoteProperty')) -and
($_.TypeNameOfValue -in #('System.IntPtr','System.Int64','System.Int32')) -and
(-Not [String]::IsNullOrWhiteSpace($_.value))
}) |ForEach-Object {
$Sum = $Sum + ($_.value -as [Int64])
}
}
"Sum = $Sum"
Comparison Result
Name Value
---- -----
BlockBTime 9.18
SameResult True
BlockATime 1.52
BlockASum 1037197387512388
Difference Block A (New Code) is ~7.66s faster than Block B (Old Code)
BlockBSum 1037197387512388
Validation & Comparison
#Stopwatch
New-Variable -Force -Name:'StopWatch' -Value:(New-Object -TypeName 'System.Diagnostics.Stopwatch')
New-Variable -Force -Name:'Result' -Value:#{
BlockATime = [Int]0
BlockASum = [Int64]0
BlockBTime = [Int]0
BlockBSum = [Int64]0
Difference = $Null
SameResult = $Null
}
New-Variable -Force -Name:'Processes' -value:(Get-Process)
$StopWatch.Restart()
New-Variable -Force -Name:'Sum' -Value:([Int64]$Null)
ForEach ($Process in $Processes) {
$Process.PSObject.Properties.Where({
($_.MemberType -in #('Property','NoteProperty')) -and
($_.TypeNameOfValue -in #('System.IntPtr','System.Int64','System.Int32')) -and
(-Not [String]::IsNullOrWhiteSpace($_.value))
}) |ForEach-Object {
$Sum = $Sum + ($_.value -as [Int64])
}
}
$Result.BlockATime = [math]::Round($StopWatch.Elapsed.TotalSeconds,2)
$Result.BlockASum = $Sum
$objs= $Processes
$StopWatch.Restart()
$columnNames = #()
foreach ($member in ($objs | get-member -memberType property, noteproperty)) {
[string]$name = $member.name
$columnNames += $name
}
[Int64 ] $sum = 0
[string] $columnName = ''
foreach ($obj in $objs) {
foreach ($columnName in $columnNames) {
$v = $obj.$columnName
# $v = $obj.psObject.members.Item($columnName).value
if ($v -eq $null) {
}
elseif ($v -is [System.IntPtr]) {
$sum = $sum + ($v -as [int64] )
}
elseif ($v -is [System.Int64] -or $v -is [System.Int32]) {
$sum = $sum + $v
}
}
}
$Result.BlockbTime = [math]::Round($StopWatch.Elapsed.TotalSeconds,2)
$Result.BlockbSum = $Sum
#Which block is faster?
If ($Result.BlockATime -lt $Result.BlockBTime) {
$Result.Difference = "Block A (New Code) is ~$(($Result.BlockBTime - $Result.BlockATime))s faster than Block B (Old Code)"
} Else {
$Result.Difference = "Block A (New Code) is ~$(($Result.BlockBTime - $Result.BlockATime))s Slower than Block B (Old Code)"
}
#Are the results the same?
If ($Result.BlockASum -eq $Result.BlockBSum) {
$Result.SameResult = $True
}

Exporting each variable value in loop

I am trying to capture the changing variable '$server' everytime the parameters go through a foreach loop. To summarize, the $sever value is always changing, and I want to capture it and add it into a collective csv file
Thank you!
Here is the code main part of the code that I have.
function Convert-QueryToObjects
{
[CmdletBinding()]
[Alias('QueryToObject')]
[OutputType([PSCustomObject])]
param
(
[Parameter(Mandatory = $false,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
Position = 0)]
[Alias('ComputerName', 'Computer')]
[string]
$Name = $env:COMPUTERNAME
)
Process
{
Write-Verbose "Running query.exe against $Name."
$Users = query user /server:$Name 2>&1
if ($Users -like "*No User exists*")
{
# Handle no user's found returned from query.
# Returned: 'No User exists for *'
Write-Error "There were no users found on $Name : $Users"
Write-Verbose "There were no users found on $Name."
}
elseif ($Users -like "*Error*")
{
# Handle errored returned by query.
# Returned: 'Error ...<message>...'
Write-Error "There was an error running query against $Name : $Users"
Write-Verbose "There was an error running query against $Name."
}
elseif ($Users -eq $null -and $ErrorActionPreference -eq 'SilentlyContinue')
{
# Handdle null output called by -ErrorAction.
Write-Verbose "Error action has supressed output from query.exe. Results were null."
}
else
{
Write-Verbose "Users found on $Name. Converting output from text."
# Conversion logic. Handles the fact that the sessionname column may be populated or not.
$Users = $Users | ForEach-Object {
(($_.trim() -replace ">" -replace "(?m)^([A-Za-z0-9]{3,})\s+(\d{1,2}\s+\w+)", '$1 none $2' -replace "\s{2,}", "," -replace "none", $null))
} | ConvertFrom-Csv
Write-Verbose "Generating output for $($Users.Count) users connected to $Name."
# Output objects.
foreach ($User in $Users)
{
Write-Verbose $User
if ($VerbosePreference -eq 'Continue')
{
# Add '| Out-Host' if -Verbose is tripped.
[PSCustomObject]#{
ComputerName = $Name
Username = $User.USERNAME
SessionState = $User.STATE.Replace("Disc", "Disconnected")
SessionType = $($User.SESSIONNAME -Replace '#', '' -Replace "[0-9]+", "")
} | Out-Host
}
else
{
# Standard output.
[PSCustomObject]#{
ComputerName = $Name
Username = $User.USERNAME
SessionState = $User.STATE.Replace("Disc", "Disconnected")
SessionType = $($User.SESSIONNAME -Replace '#', '' -Replace "[0-9]+", "")
}
}
}
}
}
}
$Servers = Get-Content 'H:\demo\computernames.txt'
foreach ($Server in $Servers)
{
if (-not( Test-Connection $Server -Count 1 -Quiet )) { continue }
if (-not( Convert-QueryToObjects $Server -ErrorAction SilentlyContinue)) {
$server | Out-File 'H:\demo\session\run1.csv' -Append
}
else
{
Convert-QueryToObjects -Name $Server | select ComputerName, Username, Sessionstate, IdleTime, ID | Export-Csv 'H:\demo\session\run.csv' -NoTypeInformation
}
}
Create an array outside of your foreach loop and add the $server variable value to the array during your foreach. At the end export the array to a csv.
Not tested, but are you wanting to do something like this?
Get-Content "H:\demo\computernames.txt" | ForEach-Object {
$computerName = $_
if ( Test-Connection $computerName -Count 1 -Quiet ) {
Convert-QueryToObjects $computerName -ErrorAction SilentlyContinue
}
else {
"$_ not pingable" | Out-File "H:\demo\session\notpingable.log" -Append
}
} | Export-Csv "H:\demo\session\run.csv" -NoTypeInformation

PSCustomObject to Hashtable

What is the easiest way to convert a PSCustomObject to a Hashtable? It displays just like one with the splat operator, curly braces and what appear to be key value pairs. When I try to cast it to [Hashtable] it doesn't work. I also tried .toString() and the assigned variable says its a string but displays nothing - any ideas?
Shouldn't be too hard. Something like this should do the trick:
# Create a PSCustomObject (ironically using a hashtable)
$ht1 = #{ A = 'a'; B = 'b'; DateTime = Get-Date }
$theObject = new-object psobject -Property $ht1
# Convert the PSCustomObject back to a hashtable
$ht2 = #{}
$theObject.psobject.properties | Foreach { $ht2[$_.Name] = $_.Value }
Keith already gave you the answer, this is just another way of doing the same with a one-liner:
$psobject.psobject.properties | foreach -begin {$h=#{}} -process {$h."$($_.Name)" = $_.Value} -end {$h}
Here's a version that works with nested hashtables / arrays as well (which is useful if you're trying to do this with DSC ConfigurationData):
function ConvertPSObjectToHashtable
{
param (
[Parameter(ValueFromPipeline)]
$InputObject
)
process
{
if ($null -eq $InputObject) { return $null }
if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
{
$collection = #(
foreach ($object in $InputObject) { ConvertPSObjectToHashtable $object }
)
Write-Output -NoEnumerate $collection
}
elseif ($InputObject -is [psobject])
{
$hash = #{}
foreach ($property in $InputObject.PSObject.Properties)
{
$hash[$property.Name] = ConvertPSObjectToHashtable $property.Value
}
$hash
}
else
{
$InputObject
}
}
}
My extremely lazy approach, enabled by a new feature in PowerShell 6:
$myhashtable = $mypscustomobject | ConvertTo-Json | ConvertFrom-Json -AsHashTable
This works for PSCustomObjects created by ConvertFrom_Json.
Function ConvertConvertFrom-JsonPSCustomObjectToHash($obj)
{
$hash = #{}
$obj | Get-Member -MemberType Properties | SELECT -exp "Name" | % {
$hash[$_] = ($obj | SELECT -exp $_)
}
$hash
}
Disclaimer: I barely understand PowerShell so this is probably not as clean as it could be. But it works (for one level only).
My code:
function PSCustomObjectConvertToHashtable() {
param(
[Parameter(ValueFromPipeline)]
$object
)
if ( $object -eq $null ) { return $null }
if ( $object -is [psobject] ) {
$result = #{}
$items = $object | Get-Member -MemberType NoteProperty
foreach( $item in $items ) {
$key = $item.Name
$value = PSCustomObjectConvertToHashtable -object $object.$key
$result.Add($key, $value)
}
return $result
} elseif ($object -is [array]) {
$result = [object[]]::new($object.Count)
for ($i = 0; $i -lt $object.Count; $i++) {
$result[$i] = (PSCustomObjectConvertToHashtable -object $object[$i])
}
return ,$result
} else {
return $object
}
}
For simple [PSCustomObject] to [Hashtable] conversion Keith's Answer works best.
However if you need more options you can use
function ConvertTo-Hashtable {
<#
.Synopsis
Converts an object to a hashtable
.DESCRIPTION
PowerShell v4 seems to have trouble casting some objects to Hashtable.
This function is a workaround to convert PS Objects to [Hashtable]
.LINK
https://github.com/alainQtec/.files/blob/main/src/scripts/Converters/ConvertTo-Hashtable.ps1
.NOTES
Base ref: https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/turning-objects-into-hash-tables-2
#>
PARAM(
# The object to convert to a hashtable
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
$InputObject,
# Forces the values to be strings and converts them by running them through Out-String
[switch]$AsString,
# If set, empty properties are Included
[switch]$AllowNulls,
# Make each hashtable to have it's own set of properties, otherwise,
# (default) each InputObject is normalized to the properties on the first object in the pipeline
[switch]$DontNormalize
)
BEGIN {
$headers = #()
}
PROCESS {
if (!$headers -or $DontNormalize) {
$headers = $InputObject | Get-Member -type Properties | Select-Object -expand name
}
$OutputHash = #{}
if ($AsString) {
foreach ($col in $headers) {
if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
$OutputHash.$col = $InputObject.$col | Out-String -Width 9999 | ForEach-Object { $_.Trim() }
}
}
} else {
foreach ($col in $headers) {
if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
$OutputHash.$col = $InputObject.$col
}
}
}
}
END {
return $OutputHash
}
}
Maybe this is overkill but I hope it Helps
Today, the "easiest way" to convert PSCustomObject to Hashtable would be so:
$custom_obj | ConvertTo-HashtableFromPsCustomObject
OR
[hashtable]$custom_obj
Conversely, you can convert a Hashtable to PSCustomObject using:
[PSCustomObject]$hash_table
Only snag is, these nifty options may not be available in older versions of PS

Convert a list of key value pairs to a hashtable

What is the best way to convert a List to a Hashtable?
Say I have a list like ("Key",$value,"Key2",$value2)
What is the shortest syntax to convert it into a Hashtable?
Try the following
$table = new-object System.Collections.Hashtable
for ( $i = 0; $i -lt $list.Length; $i += 2 ) {
$table.Add($list[$i],$list[$i+1]);
}
Function ConvertTo-Hashtable($list) {
$h = #{}
while($list) {
$head, $next, $list = $list
$h.$head = $next
}
$h
}
ConvertTo-Hashtable ("Key",1,"Key2",2)
If your list is composed of NameValuePair objects, then you can use:
Function ConvertListOfNVPTo-Hashtable($list) {
$h = #{}
$list | ForEach-Object {
$h[$_.Name] = $_.Value
}
return $h
}
$h = #{}
0..($l.count - 1) | ? {$_ -band 1} | % {$h.Add($l[$_-1],$l[$_])}
$h = #{}
0..($l.count - 1) | ? {$_ -band 1} | % {$h.($l[$_-1]) = $l[$_]}
$h = #{}
$i = 0
while ($i -lt $l.count) {$h.Add($l[$i++],$l[$i++])}
How about:
$ht = #{}
$key = "";
("Key",5,"Key2",6) | foreach `
{
if($key)
{
$ht.$key = $_;
$key="";
} else
{$key=$_}
}
function Select-Hashtable {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[PSObject[]]$InputObject,
[Parameter(Mandatory)]
[string]$Property
)
begin { $hashtable = [Hashtable]::new() }
process { $InputObject | %{ $hashtable[$_."$Property"] = $_} }
end { $hashtable }
}
then (a pointless example):
$items = Get-ChildItem -Path $path | Select-Hashtable -Property Name
$documents = $items['Documents']