Powershell how to convert System.Collections.Generic.Dictionary to PSCustomObject? - powershell

I have a System.Collections.Generic.Dictionary that I have obtained by running the code snippet below. How do I convert $Obj to a PSCustomObject?
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
$jsonserial= New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer
$jsonserial.MaxJsonLength = 67108864
$Obj = $jsonserial.DeserializeObject($JsonString)

New-Object should work from PS 2.0 onwards:
$custom = new-object PSCustomObject -Property $obj

I ended up using the following after the last line in the question
$Obj | ForEach-Object {
$props = #{}
$_.GetEnumerator() | ForEach-Object {
$props[$_.Key] = $_.Value
}
[PSCustomObject]$props
}
If there is a faster, shorter or better way, please feel free to answer

This seems to work:
$ht = #{}
$ht += $obj
[PSCustomObject]$ht
Edit:
V3 solution:
$ht = [collections.hashtable]$obj
[PSCustomObject]$ht

Related

dynamically creating key/value of an object and exporting to CSV

After getting a search result from an LDAP Server, i need to create a pscustomobject dynamically.
The Problem here is that some of the attributes are not set for all users.
this is why i cannot create the pscustomobject the traditional way.
Name = $($item.Attributes['givenname'].GetValues('string'))
Surname = $($item.Attributes['sn'].GetValues('string'))
The Attribute Name does not exist for all users and doing this throws an error.
How can i create the pscustomobject in this case where i need to add both key and value dynamically.
Here is what i have so far:
$vals="cn","tel","email","sn","givenname","ou"
$c.Bind()
$r = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $baseDN,$Filter,$scope,$attrlist
$re = $c.SendRequest($r)
foreach ($item in $re.Entries) {
foreach($attr in $vals){
if($item.Attributes.Keys -contains $attr){
$pskeys += $attr
}}
foreach($pskey in $pskeys){
$data += [pscustomobject]#{
$($pskey) = $($item.Attributes[$pskey].GetValues('string'))
}}
$pskeys = #()
}
givenname does not exist for all the users and this is why the pscustombject must be created dynamically.
I cannot use a HashTable or some kind of a List as duplicate values must be allowed. There are cases where the attributes sn and givenname are equal.
After hours of trying and failing i can only hope for the Wizards of Stackoverflow to show me how this can be achieved.
I need a pscustomobject where i can save the available attributes and skip the missing attributes dynamically. Is there a way to do this?
Regards
Try following :
$table = [System.Collections.ArrayList]::new()
foreach ($item in $re.Entries) {
$newRow = New-Object -TypeName psobject
foreach($attr in $vals){
if($item.Attributes.Keys -contains $attr){
$pskeys += $attr
}}
foreach($pskey in $pskeys){
foreach($item in $item[$pskey].Attributes.GetValues('string'))
{
$newRow | Add-Member -NotePropertyName $item.Name -NotePropertyValue $item.Value
}
}
$table.Add($newRow) | Out-Null
}
$table | Format-Table
Finally!
I have gotten it to work!
The Trick was to enclose $pskey and $item.Attributes[$pskey].GetValues('string') in $()
Without $() Add-Member was adding the properties as Arrays and not as Strings.
Here is the working Code:
$table = New-Object System.Collections.ArrayList
$c.Bind()
$r = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $baseDN,$Filter,$scope,$attrlist
$re = $c.SendRequest($r)
foreach ($item in $re.Entries) {
$newRow = New-Object -TypeName psobject
foreach($attr in $vals){
if($item.Attributes.Keys -contains $attr){
$pskeys += $attr
}}
foreach($pskey in $pskeys){
$newRow | Add-Member -NotePropertyName $($pskey) -NotePropertyValue $($item.Attributes[$pskey].GetValues('string'))
}
$table.Add($newRow) | Out-Null
$pskeys = #()
}
$table | Export-Csv -Path $ExportPath -NoTypeInformation -Encoding UTF8 -Append -Delimiter ";"
Thank You jdweng for pointing me in the right direction.
$table | Format-Table on the console and the resulting CSV after the Export look flawless now.
My Problem is solved.

Array in a foreach loop

What am I doing wrong here?
The mailbox has an active an inactive mailbox so it will return two mailboxes.
However, when trying to capture the output, I am only getting the last account in the array
Note, this is a simplified version of a larger script, but kept it simple for this example.
$guid = import-csv "c:\temp\Mailboxes.csv"
$DAta = New-Object psobject
$Data | Add-Member -MemberType NoteProperty -Name alias -Value $null
$Data | Add-Member -MemberType NoteProperty -Name guid -Value $null
$mbxcol = #()
#$data = $null
foreach ($G in $Guid){
$mbx = Get-mailbox $g.alias -IncludeInactiveMailbox
$data.alias = $mbx.alias
$data.guid = $mbx.guid
$MBXCol += $Data
}
$mbxcol
As explained in comments, every array element is a reference of the same object ($Data), a simple way to demonstrate using Object.ReferenceEquals Mehod with this example:
foreach ($item in 0..10) {
$data.Alias = 'mailbox{0}' -f $item
$data.Guid = [guid]::NewGuid()
$mbxcol += $data
}
[object]::ReferenceEquals($data, $mbxcol[0]) # -> True
As for how to simplify and make your code more efficient, do not add elements (+=) to a fixed collection (#( )):
$result = (Import-Csv "c:\temp\Mailboxes.csv").Alias |
Get-Mailbox -IncludeInactiveMailbox |
Select-Object Alias, Guid
A much more simple example of your code is:
$guid = ("A","B")
$Data = New-Object psobject
$Data | Add-Member -MemberType NoteProperty -Name alias -Value $null
$mbxcol = #()
foreach ($G in $Guid){
$mbx = $g
$data.alias = $mbx
$MBXCol += $Data
}
$mbxcol
As #Santiago mentioned in his comment, $Data is a reference to an object, so each time you update it, you overwrite it, even if it is in an array. To fix this, instantiate the object each loop as follows:
$guid = ("A","B")
$mbxcol = #()
foreach ($G in $Guid){
$Data = New-Object psobject
$Data | Add-Member -MemberType NoteProperty -Name alias -Value $null
$mbx = $g
$data.alias = $mbx
$MBXCol += $Data
}
$mbxcol

Is it possible to do an for "for" in an PSObject?

I want a output that depends on my layers, it can range from 1 to 50, so that my code is a bit more flexible.
Normally I could do that so:
$outputArray += New-Object PSObject -Property #{
'Layer 1' = ''
'Layer 2' = ''
'Layer 3' = ''
'Layer 4' = ''
...
}
But what if I have like 50 layers?
Is it possible to do a "for" or something similiar to create the object?
You should be able to solve this with something like this
$obj = New-Object PSObject
1..50 | foreach { $obj | Add-Member -MemberType NoteProperty -Name "Layer $_" -Value '' }
Alternatively like this (basically the same thing but a tad more compact code).
$obj = New-Object PSObject
1..50 | foreach { $obj | Add-Member -NotePropertyMembers #{ "Layer $_" = '' }}
As for adding values, it could be done in a few different ways but it all depends on what is known and at what time etc. You could for instance do something like this right away;
$obj = New-Object PSObject
$values = 50..1
1..50 | foreach { $obj | Add-Member -NotePropertyMembers #{ "Layer $_" = $values[$_ -1] }}
Or add values separately with $obj.'Layer 1' = "Autumn".
In addition to notjustme's answer, I would not recommend adding each property individually, but collecting them in a hashtable first, and using that for -Property, because the performance should be better.
$properties = #{}
# foreach-loop is faster than ForEach-Object cmdlet
foreach($i in 0..50) { $properties["Layer $i"] = "something"}
$obj = New-Object PSObject -Property $properties

Trouble with foreach logic

I'm trying to get a list of all virtual machines in all of my Veeam backup jobs. I wrote this
#Add Veeam snapin
Add-PSSnapin VeeamPSSnapin
#variables
$Masterlist = #()
$jobs = Get-VBRJob
foreach($job in $jobs) {
$backupJobObjects = Get-VBRJobObject -Job $job
foreach($backupJobObject in $backupJobObjects) {
$MyObject = New-Object PSObject -Property #{ Name = $backupJobObject.Name }
}
$Masterlist += $MyObject
}
$Masterlist | sort-object -Property Name
but it only spits out data from one job (there are 5). I assume this is because of some logic error in the foreach loop but I'm not seeing it. Can anyone help?
As per the per the comment from 4c74356b41
foreach($backupJobObject in $backupJobObjects) {
$MyObject = New-Object PSObject -Property #{ Name = $backupJobObject.Name }
$Masterlist += $MyObject
}
Is how the last foreach loop should look

Powershell - Change format of output

How can I change the presenation of the output my code produces:
$apps = Import-CSV apps.csv
$computers = Import-CSV compobj.csv
foreach($computer in $computers) {
$computerLob = $computer.lob
$lobApps = $apps | ? {$_.lob -eq $computerLob }
foreach($app in $lobApps){
$computerHostname = $computer.hostname
$appLocation = $app.location
$installed=Test-Path "\\$computerHostname\$appLocation"
New-Object PSObject #{Computer=$computer.hostname;App=$app.appname;Installed=$installed}
}
}
I would like for the presentation of the code to be changed. This is how it looks like:
Name Value
---- -----
Installed True
App App1
Computer 171.159.192.10
Installed True
App App2
Computer 171.159.192.10
I'd like for it to look like this:
Computer App1 App2
----------- ------ -----
171.159.192.10 True True
You're passing the hashtable to New-Object as its ctor argument instead of a property set. Change it to:
New-Object PSObject -Property #{
Computer=$computer.hostname
App=$app.appname
Installed=$installed
}
If you are on PowerShell V3, rather than use new-object you can do this:
[pscustomobject]#{Computer=$computer.hostname;App=$app.appname;Installed=$installed}
On V2, don't forget to use the -Property parameter e.g.:
new-object psobject -property #{Computer=$computer.hostname;App=$app.appname;Installed=$installed}
And to force the output order you can use Format-Table:
$obj = new-object psobject -property #{Computer=$computer.hostname;App=$app.appname;Installed=$installed}
$obj | Format-Table Computer,App,Installed
Here is what I mean (a follow-up to OP's question asked in comments, too big to fit there):
function MyFunction(){
$apps = Import-CSV apps.csv
$computers = Import-CSV compobj.csv
foreach($computer in $computers) {
$computerLob = $computer.lob
$lobApps = $apps | ? {$_.lob -eq $computerLob }
foreach($app in $lobApps){
$computerHostname = $computer.hostname
$appLocation = $app.location
$installed=Test-Path "\\$computerHostname\$appLocation"
New-Object PSObject #{Computer=$computer.hostname;App=$app.appname;Installed=$installed}
}
}
}
MyFunction | select Computer,App,Installed
A reduced test case to prove the above should work:
function MyFunction(){
New-Object PSObject -Property #{
Computer="computer"
App="app"
Installed="installed"
}
}
MyFunction | select Computer,App,Installed