PowerShell Create a series in System.Object (PSCustomObject) - powershell

Is there an easier way to accomplish this without typing the series out? I tried some ideas with arrays and dereferencing, but nothing ended up working.
$rowP = "" | Select-Object Course,"5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35"

I prefer the following approach.
Create an ordered dictionary:
$properties = [ordered]#{
Course = $null
}
Use the .. range operator to generate the range of integers 5 through 35, add empty entries to the dictionary:
5..35 |ForEach-Object {
$properties["${_}"] = $null
}
Convert the dictionary to a PSCustomObject:
$object = [pscustomobject]$properties
Alternatively, you could also use the range operator to generate the integer range and pass those as property names to Select-Object:
"" |Select-Object -Property #('Course'; 5..35 -as [string[]])

Related

Split hastable at key/value pair

How can I split a hashtable starting from a specific key/value pair?
I have hashtable like the following, just longer:
Name Value
---- -----
Name Alpha
Age 2
Position Trick
Start date 01-01-31
End date Unknown
Name Corax
Age 21
Position Sneak
Earnings 40'000
End Date Unknown
Name Horus
Age 22
Position Dead
Why Heresy
End date 03-30-30
I tried Group-Object but it failed.
I particularly wanted to separate it by Name and everything aside from Name, Age and Position are not consistent.
My actual issue is that I want to parse the hashtable for the Name and Age when Why = Heresy, and unfortunately, the original source of the data is a list of strings, which is the reason why I convert it to a hashtable.
Hashtables are not ordered, so you can't rely on a concept of "before" and "after". If you know the specific names of one complete set of keys, then you can loop through the hashtable and build two new ones, so if you wanted one hashtable to contain the Name, Age, and Position, and the other to contain everything else, you can do something like this:
$New1 = #{}
$New2 = #{}
$KeysGroup1 = #('Name','Age','Position') # This could just be one value
$MyHashTable.GetEnumerator().ForEach({
if ($_.Key -in $KeysGroup1) {
$New1[$_.Key] = $_.Value
} else {
$New2[$_.Key] = $_.Value
}
})
You can use an ordered dictionary if order is important for you. You can use a shortcut to create a literal ordered dictionary by preceding a literal hashtable with the [ordered] type accelerator:
$myOrdered = [ordered]#{ a = 1; z = 5; g = 2 }
From there you could do a similar approach to above that relies on order.
To create an ordered dictionary that isn't based on a literal:
$myOrdered = New-Object System.Collections.Specialized.OrderedDictionary
# or in PowerShell v5
$myOrdered = [System.Collections.Specialized.OrderedDictionary]::new()
$myOrdered.Add('key','value')
Edit based on comments
It sounds more like what you have is an array of hashtables and you now want to go about filtering these.
A [hashtable] can (and is often) used as a sort of proto-object, and it can be very useful for that because it often supports the same syntax, and it has built-in literal support.
But you're starting to run into their limits, and at this point I think you want to be dealing with an array of objects and not an array of hashtables.
Luckily, there are really easy ways to create objects in PowerShell right from a hashtable:
$obj = New-Object -TypeName PSObject -Property $myHash
$obj = [PSCustomObject]$myHash
$objArray = $myHashArray.ForEach({[PSCustomObject]$myHash})
Once you've got your array of objects, the real fun begins:
$heretics = $objArray.Where({$_.Why -eq 'Heresy'})
You'll notice I didn't even bother filtering out the other properties here. You shouldn't, until you really need to. Then you can use Select-Object or just access the properties you need. So for display purposes you might just do:
$heretics | Format-Table Name,Age
There's more stuff you can do with an object that you can't with hashtables, like add special types of properties:
$objArray | Add-Member -MemberType ScriptProperty -Name IsHeretic -Value { $this.Why -eq 'Heresy' } -Force
$heretics = $objArray.Where({$_.IsHeretic})
So, in the end; I figured something out, Thanks a lot to #briantist for guidance to the right direction.
My solution code is:
$startobj = (0..($hastable.count -1)) | where {$hashtable[$_].keys -like "*Name*"}
$endobj = (0..($hashtable.count -1)) | where {$hashtable[$_].keys -like "*End date*"}
$objindex = (0..($hashtable.count -1))
$numberofobjindex = 0..($hashtable.Where({$_.keys -like "*Name*"}).count - 1)
$hashtableparsed = foreach ($numba in $numberofobjindex) {
$Conv2data = $hashtable[($startobj[$numba]..$endobj[$numba])]
[PSCustomObject] #{
Name = $Conv2data.Name
Age = $Conv2data.Age
Why = $Conv2data.Why
}
}
$hashtableparsed
I realized that the hashtable data I was presented with had a repeating pattern with Name at the start of each cycle, and End date at the end.
So I basically did an indexing of the hashtable, and marked and indexed all instances where the cycle would start and end.
I then counted the cycles, and FOR EACH cycle captured the data of hashtable data in the lines particularly of that cycle and turned it into a PSCustomObject

Select-Object of multiple properties

I am trying to find an elegant way to put the metadata of a table of type System.Data.DataTable into a multi-dimensional array for easy reference in my program. My approach to the issue so far seems tedious.
Assuming $DataTable being the DataTable in question
What I tried to do so far was:
$Types = $DataTable.Columns | Select-Object -Property DataType
$Columns= $DataTable.Columns | Select-Object -Property ColumnName
$Index = $DataTable.Columns | Select-Object -Property ordinal
$AllowNull = $DataTable.Columns | Select-Object -Property AllowDbNull
Then painfully going through each array, pick up individual items and put them in my multi-dimensional array $TableMetaData.
I read in the documentation of Select-Object and it seems to me that only 1 property can be selected at 1 time? I think I should be able to do all the above more elegantly and store the information in $TableMetaData.
Is there a way to easily pick up multiple properties and put them in a multi-dimensional array in 1 swoop?
I read the documentation of Select-Object and it seems to me that only 1 property can be selected at 1 time?
This is not true, Select-Object can take any number of arguments to the -Property parameter
$ColumnInfo = $DataTable.Columns | Select-Object -Property DataType,ColumnName,ordinal,AllowDbNull
Now $ColumnInfo will contain one object for each column, having all 4 properties.
Rather than using a multi-dimensional array, you should consider using a hashtable (#{}, an unordered dictionary):
$ColumnInfo = $DataTable.Columns | ForEach-Object -Begin { $ht = #{} } -Process {
$ht[$_.ColumnName] = $_
} -End { return $ht }
Here, we create an empty hashtable $ht (the -Begin block runs just once), then store each column object in $ht using the ColumnName as the key, and finally return $ht, storing it in $ColumnInfo.
Now you can reference metadata about each column by Name:
$ColumnInfo.Column2
# or
$ColumnInfo["Column2"]
One easy way to do this is to create an "empty" variable with Select-Object. Here is a sample command:
$DataTableReport = "" | Select-Object -Property DataType, ColumnName, ordinal, AllowDbNull
Then, link the $DataTableReport to the $Types, $Columns, $Index, and the $AllowNull properties as shown below:
$DataTableReport.Types = $DataTable.DataType
$DataTableReport.Columns = $DataTable.ColumnName
$DataTableReport.Index = $DataTable.ordinal
$DataTableReport.AllowNull = $DataTable.AllowDbNull
Finally, call the DataTableReport variable.
$DataTableReport # will display all the results in a tabular form.

In Powershell, is there a better way to store/find data in an n-dimensional array than a custom object

I find myself continually faced with the need to store mixed-type data in some kind of a structure for later lookup.
For a recent example, I am performing data migration and I will store the old UUID, new UUID, source environment, target environment, and schema for an unknown number of entries.
I have been meeting this need by creating an array and inserting System.Objects with NoteProperty members for each of the columns of data.
This strikes me as a very clumsy approach but I feel like I may be limited by Powershell's functionality. If I need to, for example, locate all entries that used a particular schema, I write a foreach loop that sticks each entry with a matching schema name in a whole new array that I can return. I would really like the ability to more easily search for all objects that contain a member matching a particular value, modify existing members, etc.
Is there a better built-in data structure that will suit my needs, or is creating a custom object the right thing to do?
For reference, I'm doing something like this to create my structure:
$objectArray= #();
foreach(thing to process){
$tempObj = New-Object System.Object;
$tempObj | Add-Member -MemberType NoteProperty -Name "membername" -Value xxxxx
....repeat for each member...
$objectArray += $tempObj
}
If I need to find something in it, I then have to:
$matchingObjs = #()
foreach ($obj in $objectArray){
if($obj.thing -eq value){$matchingObjs += $obj}
}
This really sucks and I know there has to be a more elegant way. I'm still fairly new to powershell so I don't know what utilities it has to help me. I'm using v5.
With PowerShell 3.0 you could use a [PSCustomObject], here's an article on the different object creation methods.
Also setting the array equal to the output of the foreach loop will be more efficient than repeatedly recreating an array with +=.
$objectArray = foreach ($item in $collection) {
[pscustomobject]#{
"membername" = "xxxxx"
}
}
The Where-Object cmdlet or the .where() method looks like what you need in your second loop.
$matchingObjs = $objectArray | Where-Object {$_.thing -eq "value"}
It also sounds like you could use Where-Object/.where() to filter the initial data and just create an object which matches what you are looking for. For example:
$matchingObjs = $InputData |
Where-Object {$_.thing -eq "value"} |
ForEach-Object {
[pscustomobject]#{
"membername" = xxxxx
}
}
If your data can be expressed as key value pairs, then a hashtable will be the most efficient, see about_Hash_Tables for more info.
There is no built-in way to do what you are asking. One way is to segment your data into separate hashtables so you can do easy lookups by a common key, say the ID.
# Create a hastable for the IDs
$ids = #{};
foreach(thing to process){
$ids.Add($uid, 'Value')
}
# Find the $uid exists
$keyExists = $ids.Keys -Contains $uid
# Find value of stored for $uid
$keyValue = $ids[$uid]
As a side note, you don't have to create Syste.Object, you can simple do this:
$objectArray = #();
gci | % {
$objectArray += #{
'Key1' = 'Value 1'
'Key2' = 'Value 2'
}
}
If you need to compare complex objects, you can build them with #{} and then use Compare-Object on the two objects, just another idea.
For example, this will get a file listing of two different directories, and tell me what file exists or doesn't exist between the two directories:
$packages = (gci $boxStarterRepo -Recurse *.nuspec | Select-Object -ExpandProperty Name) -replace '.nuspec', ''
$packages += (gci $boxStarterPrivateRepo -Recurse *.nuspec | Select-Object -ExpandProperty Name) -replace '.nuspec', ''
$packages = $packages | Sort-Object
Compare-Object $packages $done

How to check multiple values in variable against multiple values in hash table or pscustomobject?

I would like to know how I can check multiple values against multiple values contained in a hash table or pscustomobject?
Just for context, the cmdlets used are for SharePoint.
Here`s my code so far:
$usersSearched = [pscustomobject]#{
DisplayName = (#("Administrator";"Service Administrator";"Company
Administrator"))
LoginName = (#("s-1-5-21-2098222698-275879357-2441446288-39577944";"s-1-5-
21-2098222698-275179357-2441446288-39696162";"s-1-5-21-2098262698-275879357-
2441446288-14998143"))
}
#Returns object with one or more values and has a .LoginName property
$result = Get-SPOUser -Site $site | Where-Object {$_.IsSiteAdmin -eq $True}
If ($usersSearched.LoginName -in $result.LoginName)
#Whatever
{
My propblem is that I am unable to get the condition to return true.
If there`re easier ways to execute this seemingly simple task, please let me know.
First, have to separate entries within an array using a comma, not a semicolon:
$usersSearched = [PsCustomObject]#{
DisplayName = #("Administrator","Service Administrator","Company Administrator")
LoginName = #("s-1-5-21-2098222698-275879357-2441446288-39577944",
"s-1-5-21-2098222698-275179357-2441446288-39696162",
"s-1-5-21-2098262698-275879357-2441446288-14998143")
}
Second, you have to use -contains instead of -in (or you swap the values):
If ($usersSearched.LoginName -contains $result.LoginName)

Powershell Multidimensional Arrays

I have a way of doing Arrays in other languagues like this:
$x = "David"
$arr = #()
$arr[$x]["TSHIRTS"]["SIZE"] = "M"
This generates an error.
You are trying to create an associative array (hash). Try out the following
sequence of commands
$arr=#{}
$arr["david"] = #{}
$arr["david"]["TSHIRTS"] = #{}
$arr["david"]["TSHIRTS"]["SIZE"] ="M"
$arr.david.tshirts.size
Note the difference between hashes and arrays
$a = #{} # hash
$a = #() # array
Arrays can only have non-negative integers as indexes
from powershell.com:
PowerShell supports two types of multi-dimensional arrays: jagged arrays and true multidimensional arrays.
Jagged arrays are normal PowerShell arrays that store arrays as elements. This is very cost-effective storage because dimensions can be of different size:
$array1 = 1,2,(1,2,3),3
$array1[0]
$array1[1]
$array1[2]
$array1[2][0]
$array1[2][1]
True multi-dimensional arrays always resemble a square matrix. To create such an array, you will need to access .NET. The next line creates a two-dimensional array with 10 and 20 elements resembling a 10x20 matrix:
$array2 = New-Object 'object[,]' 10,20
$array2[4,8] = 'Hello'
$array2[9,16] = 'Test'
$array2
for a 3-dimensioanl array 10*20*10
$array3 = New-Object 'object[,,]' 10,20,10
To extend on what manojlds said above is that you can nest Hashtables. It may not be a true multi-dimensional array but give you some ideas about how to structure the data. An example:
$hash = #{}
$computers | %{
$hash.Add(($_.Name),(#{
"Status" = ($_.Status)
"Date" = ($_.Date)
}))
}
What's cool about this is that you can reference things like:
($hash."Name1").Status
Also, it is far faster than arrays for finding stuff. I use this to compare data rather than use matching in Arrays.
$hash.ContainsKey("Name1")
Hope some of that helps!
-Adam
Knowing that PowerShell pipes objects between cmdlets, it is more common in PowerShell to use an array of PSCustomObjects:
$arr = #(
[PSCustomObject]#{Name = 'David'; Article = 'TShirt'; Size = 'M'}
[PSCustomObject]#{Name = 'Eduard'; Article = 'Trouwsers'; Size = 'S'}
)
Or for older PowerShell Versions (PSv2):
$arr = #(
New-Object PSObject -Property #{Name = 'David'; Article = 'TShirt'; Size = 'M'}
New-Object PSObject -Property #{Name = 'Eduard'; Article = 'Trouwsers'; Size = 'S'}
)
And grep your selection like:
$arr | Where {$_.Name -eq 'David' -and $_.Article -eq 'TShirt'} | Select Size
Or in newer PowerShell (Core) versions:
$arr | Where Name -eq 'David' | Where Article -eq 'TShirt' | Select Size
Or (just get the size):
$arr.Where{$_.Name -eq 'David' -and $_.Article -eq 'TShirt'}.Size
Addendum 2020-07-13
Syntax and readability
As mentioned in the comments, using an array of custom objects is straighter and saves typing, if you like to exhaust this further you might even use the ConvertForm-Csv (or the Import-Csv) cmdlet for building the array:
$arr = ConvertFrom-Csv #'
Name,Article,Size
David,TShirt,M
Eduard,Trouwsers,S
'#
Or more readable:
$arr = ConvertFrom-Csv #'
Name, Article, Size
David, TShirt, M
Eduard, Trouwsers, S
'#
Note: values that contain spaces or special characters need to be double quoted
Or use an external cmdlet like ConvertFrom-SourceTable which reads fixed width table formats:
$arr = ConvertFrom-SourceTable '
Name Article Size
David TShirt M
Eduard Trouwsers S
'
Indexing
The disadvantage of using an array of custom objects is that it is slower than a hash table which uses a binary search algorithm.
Note that the advantage of using an array of custom objects is that can easily search for anything else e.g. everybody that wears a TShirt with size M:
$arr | Where Article -eq 'TShirt' | Where Size -eq 'M' | Select Name
To build an binary search index from the array of objects:
$h = #{}
$arr | ForEach-Object {
If (!$h.ContainsKey($_.Name)) { $h[$_.Name] = #{} }
If (!$h[$_.Name].ContainsKey($_.Article)) { $h[$_.Name][$_.Article] = #{} }
$h[$_.Name][$_.Article] = $_ # Or: $h[$_.Name][$_.Article]['Size'] = $_.Size
}
$h.david.tshirt.size
M
Note: referencing a hash table key that doesn't exist in Set-StrictMode will cause an error:
Set-StrictMode -Version 2
$h.John.tshirt.size
PropertyNotFoundException: The property 'John' cannot be found on this object. Verify that the property exists.
Here is a simple multidimensional array of strings.
$psarray = #(
('Line' ,'One' ),
('Line' ,'Two')
)
foreach($item in $psarray)
{
$item[0]
$item[1]
}
Output:
Line
One
Line
Two
Two-dimensional arrays can be defined this way too as jagged array:
$array = New-Object system.Array[][] 5,5
This has the nice feature that
$array[0]
outputs a one-dimensional array, containing $array[0][0] to $array[0][4].
Depending on your situation you might prefer it over $array = New-Object 'object[,]' 5,5.
(I would have commented to CB above, but stackoverflow does not let me yet)
you could also uses System.Collections.ArrayList to make a and array of arrays or whatever you want.
Here is an example:
$resultsArray= New-Object System.Collections.ArrayList
[void] $resultsArray.Add(#(#('$hello'),2,0,0,0,0,0,0,1,1))
[void] $resultsArray.Add(#(#('$test', '$testagain'),3,0,0,1,0,0,0,1,2))
[void] $resultsArray.Add("ERROR")
[void] $resultsArray.Add(#(#('$var', '$result'),5,1,1,0,1,1,0,2,3))
[void] $resultsArray.Add(#(#('$num', '$number'),3,0,0,0,0,0,1,1,2))
One problem, if you would call it a problem, you cannot set a limit. Also, you need to use [void] or the script will get mad.
Using the .net syntax (like CB pointed above)
you also add coherence to your 'tabular' array...
if you define a array...
and you try to store diferent types
Powershell will 'alert' you:
$a = New-Object 'byte[,]' 4,4
$a[0,0] = 111; // OK
$a[0,1] = 1111; // Error
Of course Powershell will 'help' you
in the obvious conversions:
$a = New-Object 'string[,]' 2,2
$a[0,0] = "1111"; // OK
$a[0,1] = 111; // OK also
Another thread pointed here about how to add to a multidimensional array in Powershell. I don't know if there is some reason not to use this method, but it worked for my purposes.
$array = #()
$array += ,#( "1", "test1","a" )
$array += ,#( "2", "test2", "b" )
$array += ,#( "3", "test3", "c" )
Im found pretty cool solvation for making arrays in array.
$GroupArray = #()
foreach ( $Array in $ArrayList ){
$GroupArray += #($Array , $null)
}
$GroupArray = $GroupArray | Where-Object {$_ -ne $null}
Lent from above:
$arr = ConvertFrom-Csv #'
Name,Article,Size
David,TShirt,M
Eduard,Trouwsers,S
'#
Print the $arr:
$arr
Name Article Size
---- ------- ----
David TShirt M
Eduard Trouwsers S
Now select 'David'
$arr.Where({$_.Name -eq "david"})
Name Article Size
---- ------- ----
David TShirt M
Now if you want to know the Size of 'David'
$arr.Where({$_.Name -eq "david"}).size
M