How to get the name of a PsCustomObject? - powershell

I have a Powershell-script which includes a lot of PsCustomObjects like this (names do not have a specific pattern):
$myObject1 = [PSCustomObject]#{
Name = 'Kevin'
State = 'Texas'
}
$myObject2 = [PSCustomObject]#{
Name = 'Peter'
Lastname = 'Fonda'
State = 'Florida'
}
Now I need to convert this programmatically into the following object-format to have a global hashtable:
$result = #{
'myObject1.Name' = 'Kevin'
'myObject1.State' = 'Texas'
'myObject2.Name' = 'Peter'
'myObject2.Lastname' = 'Fonda'
'myObject2.Sate' = 'Florida'
}
For this task I need a loop in which I can either read the name of each PsCustomOject or I need to specify the names of all object as a string-array and lookup the object-properties with the matching name.
The loop-constructor could look something like this:
$result = #{}
foreach($name in #('myObject1','myObject2')) {
$obj = myMissingFunction1 $name
foreach($p in $obj.PsObject.Properties) {
$result["$name.$($p.Name)"] = $p.Value
}
}
or this
$result = #{}
foreach($obj in #($myObject1, $myObject2)) {
$name = myMissingFunction2 $obj
foreach($p in $obj.PsObject.Properties) {
$result["$name.$($p.Name)"] = $p.Value
}
}
Unfortunately I cannot bring any of the two approaches to life. Can someone please help?

Here is how you could do it .
$Result = [Ordered]#{}
$index = 0
Foreach ($obj in $ArrayOfObject) {
foreach ($prop in ($obj | Get-Member -MemberType NoteProperty).Name) {
$Result."SomeObject$Index.$Prop" = $obj.$prop
}
$index += 1
}
Result
Name Value
---- -----
SomeObject0.Name Kevin
SomeObject0.State Texas
SomeObject1.Lastname Fonda
SomeObject1.Name Peter
SomeObject1.State Florida
Dataset used for this example
$ArrayOfObject = #(
[PSCustomObject]#{
Name = 'Kevin'
State = 'Texas'
}
[PSCustomObject]#{
Name = 'Peter'
Lastname = 'Fonda'
State = 'Florida'
}
)

Solved it finally. I completely forgot the "Get-Variable" function:
$result = #{}
foreach($name in #('myObject1','myObject2')) {
$obj = (Get-Variable $name).Value
foreach($p in $obj.PsObject.Properties) {
$result["$name.$($p.Name)"] = $p.Value
}
}

Related

Powershell HashTable default sorting does not correspond with Hashtable constructor [duplicate]

Is there a way to keep the order of keys in a hashtable as they were added? Like a push/pop mechanism.
Example:
$hashtable = #{}
$hashtable.Add("Switzerland", "Bern")
$hashtable.Add("Spain", "Madrid")
$hashtable.Add("Italy", "Rome")
$hashtable.Add("Germany", "Berlin")
$hashtable
I want to retain the order in which I've added the elements to the hashtable.
There is no built-in solution in PowerShell V1 / V2. You will want to use the .NET
System.Collections.Specialized.OrderedDictionary:
$order = New-Object System.Collections.Specialized.OrderedDictionary
$order.Add("Switzerland", "Bern")
$order.Add("Spain", "Madrid")
$order.Add("Italy", "Rome")
$order.Add("Germany", "Berlin")
PS> $order
Name Value
---- -----
Switzerland Bern
Spain Madrid
Italy Rome
Germany Berlin
In PowerShell V3 you can cast to [ordered]:
PS> [ordered]#{"Switzerland"="Bern"; "Spain"="Madrid"; "Italy"="Rome"; "Germany"="Berlin"}
Name Value
---- -----
Switzerland Bern
Spain Madrid
Italy Rome
Germany Berlin
You can use an ordered dictionary instead:
Like this:
$list = New-Object System.Collections.Specialized.OrderedDictionary
$list.Add("Switzerland", "Bern")
$list.Add("Spain", "Madrid")
$list.Add("Italy", "Rome")
$list.Add("Germany", "Berlin")
$list
You can give one sequential key as you add elements:
$hashtable = #{}
$hashtable[$hashtable.count] = #("Switzerland", "Bern")
$hashtable[$hashtable.count] = #("Spain", "Madrid")
$hashtable[$hashtable.count] = #("Italy", "Rome")
$hashtable[$hashtable.count] = #("Germany", "Berlin")
$hashtable
Then, you can get elements sorted by the key:
echo "`nHashtable keeping the order as they were added"
foreach($item in $hashtable.getEnumerator() | Sort Key)
{
$item
}
Here is a simple routine that works for me.
function sortedKeys([hashtable]$ht) {
$out = #()
foreach($k in $ht.keys) {
$out += $k
}
[Array]::sort($out)
return ,$out
}
and the call to use it
forEach($k in (& sortedKeys $ht)) {
...
}
For compatibility with older PowerShell versions you might consider this cmdlet:
Function Order-Keys {
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)][HashTable]$HashTable,
[Parameter(Mandatory = $false, Position = 1)][ScriptBlock]$Function,
[Switch]$Descending
)
$Keys = $HashTable.Keys | ForEach {$_} # Copy HashTable + KeyCollection
For ($i = 0; $i -lt $Keys.Count - 1; $i++) {
For ($j = $i + 1; $j -lt $Keys.Count; $j++) {
$a = $Keys[$i]
$b = $Keys[$j]
If ($Function -is "ScriptBlock") {
$a = $HashTable[$a] | ForEach $Function
$b = $HashTable[$b] | ForEach $Function
}
If ($Descending) {
$Swap = $a -lt $b
}
Else
{
$Swap = $a -gt $b
}
If ($Swap) {
$Keys[$i], $Keys[$j] = $Keys[$j], $Keys[$i]
}
}
}
Return $Keys
}
This cmdlet returns a list of keys ordered by the function definition:
Sort by name:
$HashTable | Order-Keys | ForEach {Write-Host $_ $HashTable[$_]}
Germany Berlin
Italy Rome
Spain Madrid
Switzerland Bern
Sort by value:
$HashTable | Order-Keys {$_} | ForEach {Write-Host $_ $HashTable[$_]}
Germany Berlin
Switzerland Bern
Spain Madrid
Italy Rome
You might also consider to nest hash tables:
$HashTable = #{
Switzerland = #{Order = 1; Capital = "Berne"}
Germany = #{Order = 2; Capital = "Berlin"}
Spain = #{Order = 3; Capital = "Madrid"}
Italy = #{Order = 4; Capital = "Rome"}
}
E.g. sort by (hashed) order property and return the key (country):
$HashTable | Order-Keys {$_.Order} | ForEach {$_}
Or sort (descending) by the predefined capital:
$HashTable | Order-Keys {$_.Capital} -Descending | ForEach {$_}
The PowerShell 1 way is to add a hashtable member to retain the add order. There is no need to use System.Collections.Specialized.OrderedDictionary:
$Hash = New-Object PSObject
$Hash | Add-Member -MemberType NoteProperty -Name key1 -Value val1
$Hash | Add-Member -MemberType NoteProperty -Name key2 -Value val2
$Hash | Add-Member -MemberType NoteProperty -Name key3 -Value val3
function global:sortDictionaryByKey([hashtable]$dictionary)
{
return $dictionary.GetEnumerator() | sort -Property name;
}

Powershell - Expand subArray and rename fields

I have a source array like this
$myArray = (
#{ id = "1"; name = "first item";
subArray = (
#{ id = "A"; name = "first subitem" },
#{ id = "B"; name = "second subitem" } )
},
#{ id = "2"; name = "second item";
subArray = (
#{ id = "C"; name = "third subitem" },
#{ id = "D"; name = "fourth subitem" } )
}
)
I need to extract the relations between the parent and child arrays like following:
source target
-----------------
1 A
1 B
2 C
2 D
I have come up with a following code to achieve that
$myArray | ForEach-Object {
$id = $_.id
$_.subArray | ForEach-Object {
#{
source = $id
target = $_.id
}
}
}
I wonder if there is some more straight forward solution.
Edit:
Based on Marsze answer - slightly modified solution
$myArray| ForEach-Object
{$a=$_; $a.subArray | Select-Object #{n="source";e={$a.id}},#{n="target";e={$_.id}}
}
Looks pretty straightforward to me. You could use foreach loops instead of the pipeline cmdlet, which is faster and has the advantage, that you can reference variables on each level directly.
Also I recommended converting to PSCustomObject for the proper output format:
foreach ($a in $myArray) {
foreach ($b in $a.subArray) {
[PSCustomObject]#{source = $a.id; target = $b.id}
}
}
Alternatively with New-Object:
foreach ($a in $myArray) {
foreach ($b in $a.subArray) {
New-Object PSObject -Property #{source = $a.id; target = $b.id}
}
}
Or the Select-Object version:
foreach ($a in $myArray) {
$a.subArray | select #{n="source";e={$a.id}},#{n="target";e={$_.id}}
}

Array does not wipe

I don't understand why my $ReturnedAnyTypeMembers array doesn't wipe every time the function recurses. I don't want it to wipe. Its actually doing what I want it to do right now, which is keep an accurate growing list. I just don't understand why the contents of the array don't wipe every time the function is called. Any help understanding?
function Get-GroupMembers {
Param($Group)
[System.Collections.ArrayList] $ReturnedAnyTypeMembers = #()
$GroupMembersArray = gam print group-members group $Group | ConvertFrom-Csv
foreach ($GroupMember in $GroupMembersArray) {
$GroupMemberType = ($GroupMember.type)
$GroupMemberEmail = ($GroupMember.email)
$GroupMemberIsAGroup = ($GroupMemberType -eq "GROUP")
$ReturnedAnyTypeMembers.Add($GroupMember) | Out-Null
if($GroupMemberIsAGroup) {
Get-GroupMembers $GroupMemberEmail
}
}
$ReturnedGroupMembers = #{
"all" = $ReturnedAnyTypeMembers
}
Return $ReturnedGroupMembers
}
Is this the result you are after...
function Get-GroupMembers
{
Param($Group)
[System.Collections.ArrayList] $ReturnedAnyTypeMembers = #()
$GroupMembersArray = Get-LocalGroupMember -Group $Group
foreach ($GroupMember in $GroupMembersArray) {
$GroupMemberType = ($GroupMember.type)
$GroupMemberEmail = ($GroupMember.email)
$GroupMemberIsAGroup = ($GroupMemberType -eq "GROUP")
$ReturnedAnyTypeMembers.Add($GroupMember) | Out-Null
if($GroupMemberIsAGroup) {
Get-GroupMembers $GroupMemberEmail
}
}
$ReturnedGroupMembers = #{
"all" = $ReturnedAnyTypeMembers
}
Return $ReturnedGroupMembers
}
'Administrators','Users' |
ForEach-Object {Get-GroupMembers -Group $PSItem} |
Format-Table -AutoSize
# Results
<#
Name Value
---- -----
all {104DB2FE-76B8-4\Administrator, 104DB2FE-76B8-4\WDAGUtilityAccount}
all {NT AUTHORITY\Authenticated Users, NT AUTHORITY\INTERACTIVE}
#>
... or something else?

Import-CSV Nested HashTable

Say I have a CSV file that looks like:
arch,osversion,kb
32,6.1,KB1,http://kb1
32,6.2,KB2,http://kb2
64,6.1,KB3,http://kb3
64,6.2,KB4,http://kb4
How would this CSV get imported into structured hash table that looks like this?
32 -> 6.1 -> KB1 -> http://kb1
-> 6.2 -> KB2 -> http://kb2
64 -> 6.1 -> KB3 -> http://kb3
-> 6.2 -> KB4 -> http://kb4
The command below yields http://kb1:
$data['32'].'6.1'.'KB1'
Probably Group-Object is what you want.
$csv = #'
arch,osversion,kb,link
32,6.1,KB1,http://kb1
32,6.2,KB2,http://kb2
64,6.1,KB3,http://kb3
64,6.2,KB4,http://kb4
'#
$data = ConvertFrom-Csv $csv
$data | Group-Object -Property arch
Or maybe closer to what you want to query:
$groups = $data | Group-Object -Property arch, osversion, kb
($groups | ? Name -eq '32, 6.1, KB1').Group.link
You could even use variables...
$a = '32'
$o = '6.1'
$k = 'KB1'
($groups | ? Name -eq "$a, $o, $k").Group.link
From this, you can determine if such a pattern works for you.
Interesting task. The following code snippet could help (solves arch duplicates):
Remove-Variable data*, aux* -ErrorAction SilentlyContinue ### clear for debugging purposes
$datacsv = #'
arch,osversion,kb,link
32,6.1,KB1,http://kb1
32,6.2,KB2,http://kb2
64,6.1,KB3,http://kb3
64,6.2,KB4,http://kb4
'#
$datac = ConvertFrom-Csv $datacsv
$datag = #{}
$datac | ForEach-Object {
$auxLeaf = #{ $_.kb = $_.link }
$auxParent = #{ $_.osversion = $auxLeaf }
if ( $datag.ContainsKey( $_.arch) ) {
$auxParent += $datag[ $_.arch]
}
$datag.Set_Item( $_.arch, $auxParent )
}
Then, $datag['32']['6.1']['KB1'] returns desired value http://kb1
Another interesting problem: solve osversion duplicates in a particular arch:
Remove-Variable data*, aux* -ErrorAction SilentlyContinue ### clear for debugging purposes
$datacsv = #'
arch,osversion,kb,link
32,6.1,KB1,http://kb1
32,6.1,KB5,http://kb5
32,6.1,KB7,http://kb7
32,6.2,KB2,http://kb2
64,6.1,KB3,http://kb3
64,6.2,KB4,http://kb4
'#
$datac = ConvertFrom-Csv $datacsv
$datag = #{}
$datac | ForEach-Object {
$auxLeaf = #{ $_.kb = $_.link }
$auxParent = #{ $_.osversion = $auxLeaf }
if ( $datag.ContainsKey( $_.arch) ) {
if ( $datag[$_.arch].ContainsKey($_.osversion) ) {
$auxLeaf += $datag[$_.arch][$_.osversion]
$auxParent = #{ $_.osversion = $auxLeaf }
} else {
$auxParent += $datag[ $_.arch]
}
}
$datag.Set_Item( $_.arch, $auxParent )
}
The latter code snippet is roughly equivalent to
$datag =
#{
'32' = #{ '6.1' = #{ 'KB1'='http://kb1';
'KB5'='http://kb5';
'KB7'='http://kb7' };
'6.2' = #{ 'KB2'='http://kb2' }
};
'64' = #{ '6.1' = #{ 'KB3'='http://kb3' };
'6.2' = #{ 'KB4'='http://kb4' }
}
}

powershell passwordlastset export to spread sheet

Why is this coming up blank for all machines in the passwordlastset attribute, when I export it in the csv file? Everything else works perfectly.
$Searcher = New-ObjectSystem.DirectoryServices.DirectorySearcher([ADSI]"LDAP://dc=amers,dc=jhe,dc=domain,dc=com")
$Searcher.Filter = "(&(objectCategory=computer)(objectClass=computer)(!UserAccountControl:1.2.840.113556.1.4.803:=2)(operatingSystem=Windows XP*))"
$Searcher.PageSize = 100000
$results = $Searcher.Findall()
$results | ForEach-Object { $_.GetDirectoryEntry() } |
select #{ n = 'CN'; e = { ($_.CN) } },
#{ n = 'DistinguishedName'; e = { $_.DistinguishedName } },
#{ n = 'extensionattribute7'; e = { $_.extensionattribute7 } },
#{ n = 'LastLogon'; e = { [DateTime]::FromFileTime($_.PasswordLastSet) } },
#{ n = 'OperatingSystem'; e = { $_.OperatingSystem } } |
Export-Csv 'C:\temp\WindowsXP_Only.csv' -NoType -Force
By default not all properties are returned, so you need to specify the additional properties you want.
Also, if you're looking for the last logon date (per your output), you should be using lastLogonTimestamp and not PasswordLastSet.
Here's an example using Get-ADComputer, which I greatly prefer over using older methods of searching AD. Just add your Export-CSV when you're happy with the results.
$results = get-adcomputer -Filter "operatingSystem -like 'Windows XP*'" -properties cn,lastlogontimestamp,operatingsystem,extensionattribute7,PasswordLastSet -searchbase "dc=amers,dc=jhe,dc=domain,dc=com";
$results |
select #{ n = 'CN'; e = { ($_.cn) } },
#{ n = 'DistinguishedName'; e = { $_.DistinguishedName } },
#{ n = 'extensionattribute7'; e = { $_.extensionattribute7 } },
#{ n = 'LastLogon'; e = { [DateTime]::FromFileTime($_.lastLogonTimestamp) } },
#{ n = 'PasswordLastSet'; e = { [DateTime]::FromFileTime($_.PasswordLastSet) } },
#{ n = 'OperatingSystem'; e = { $_.OperatingSystem } }
You might also find this script useful