Powershell: How to dynamically build array or objects? - arrays

I would like to collect some information about hosts in the domain, so I am trying to write something like this:
# declare array for storing final data
$servers_list = #()
#start with a list of servers and go through collecting the info
$servers | ForEach-Object {
$sys = Get-WmiObject Win32_computersystem -ComputerName $_
# create new custom object to store information
$server_obj = New-Object –TypeName PSObject
$server_obj | Add-Member –MemberType NoteProperty –Name Domain –Value $sys.Domain
# .... add all other relevant info in the same manner
# Add server object to the array
$servers_list += $server_obj
}
The problem with this code is that I pass a reference to the object into array and not the actual object. So by the time my loop is finished I end up with an array that contains rows that look all the same :(
Any idea how to pass actual object into array and not just a reference to it?
Another thought is to dynamically declare new object instead of using $server_obj variable every time but I am not sure how to do this either...
Thanks!!!

You can build an array of objects and keep dynamically adding information to them like this:
#This will be your array of objects
#In which we will keep adding objects from each computer
$Result = #()
#start with a list of servers and go through collecting the info
$servers | ForEach-Object {
$sys = Get-WmiObject Win32_computersystem -ComputerName $_
# create new custom object to keep adding store information to it
$Result += New-Object –TypeName PSObject -Property #{Domain = $sys.Domain;
Name = $sys.Name;
SystemType = $sys.SystemType
}
}
# Get back the objects
$Result
Where Domain,Name and SystemType are the properties that you want to associate with the objects.

It sounds like it is passing a reference, but I don't think it's the object that's being passed as a reference, but the property values. There are discrete objects, but they all have the same reference for their property values, so they all look the same. If that's the case,
$server_obj | Add-Member –MemberType NoteProperty –Name Domain –Value "$($sys.Domain)"
should make the value a string, which is a value type and won't change.

You're making this a little harder than it should be. Pass the server names from a query, csv or list then iterate over them. Select what you want from the result.
$info = "server1", "server2" | ForEach-Object{Get-WmiObject -Class win32_computersystem -ComputerName $_ } | Select-Object Domain, Name, Systemtype
$info[1].Domain will output domain.com

Related

Trying to output a custom powershell object where I can align each line of two different variables containing Category:Description

I'm trying to do an network access control audit by grabbing a user's AD groups, their descriptions and then output them in a way shown by this example:
[User]
#[1]Groups : #[1]GroupDescription
#[2]...
#[3]...
Below is what I have at the moment.
$UserGroups = #{
User = Read-Host -Prompt "What user do You want to look up Access for?"
Groups = (Get-ADUser $User -Properties MemberOf).MemberOf
GroupsDescriptions = (Get-ADUser $User -Properties MemberOf).MemberOf | % {(Get-ADGroup $_ -Properties *).description}
}
$Object = New-Object psobject -Property $UserGroups
$Object | format-table | Export-Csv c:\tmp\test.csv
Though the output is very strange. I don't understand it. Below is a result of Get-Content C:tmp\test.csv
#TYPE Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
"ClassId2e4f51ef21dd47e99d3c952918aff9cd","pageHeaderEntry","pageFooterEntry","autosizeInfo","shapeInfo","groupingEntry"
"033ecb2bc07a4d43b5ef94ed5a35d280",,,,"Microsoft.PowerShell.Commands.Internal.Format.TableHeaderInfo",
"9e210fe47d09416682b841769c78b8a3",,,,,
"27c87ef9bbda4f709f6b4002fa4af63c",,,,,
"4ec4f0187cb04f4cb6973460dfe252df",,,,,
"cf522b78d86c486691226b40aa69e95c",,,,,
I have tried outputting to a .txt file using Out-file, but I always get each property cut off with a ... at the end. I've used the -Autosize and -Expand when formatting the data before piping it to the export line.
Any Suggestions or advice would be extremely helpful.
Things I'll be Looking at later
Go through each line in PowerShell object and extract variables
Powershell & ActiveDirectory - trying to output users in a group and their membership
Out-file crops my text when trying to output a table
Thanks!
As stated, only ever use Format-* cmdlets to produce for-display output, never for outputting data that must be processed programmatically later. What Format-Table outputs are objects representing formatting instructions, and it is their properties that ended up in your CSV file - see this answer for more information.
In order to include collections (arrays) in CSV output, you must convert them to a single string, using a self-chosen separator. Otherwise, Export-Csv simply calls .ToString() on the collection object itself, which yields the collection's type name, and no information about its elements.
Therefore, use something like the following, which uses ', ' as the separator string to represent the group names and descriptions in a single column each:
$UserGroups = [pscustomobject] #{
User = ($user = Read-Host -Prompt "What user do You want to look up Access for?")
Groups = ($groups = (Get-ADUser $User -Properties MemberOf).MemberOf) -join ', '
GroupsDescriptions = (
$groups | ForEach-Object { (Get-ADGroup $_ -Properties *).Description }
) -join ', '
}
$UserGroups | Export-Csv c:\tmp\test.csv
Note:
[pscustomobject] #{ ... } is used to directly construct a custom object, which is syntactic sugar available since PowerShell v3 that is simpler and more efficient than a New-Object call.
In order to use the result from your Read-Host call in later properties of your object definition, you must cache it in aux. variable $user (note that enclosing the assignment in (...) passes its value through.
Similarly, the result of the Get-ADUser call is cached in aux. variable $groups, so that it doesn't have to be repeated in the GroupsDescriptions value.
However, as zett42 points out, it may be cleaner to make the $user = ... and $groups = ... assignments separate statements and place them before the object construction.
The problem is that you pipe to Format-Table before you pipe to Export-Csv. Only use Format-Table for displaying things on screen. The fix is to just remove that.
$Object | Export-Csv c:\tmp\test.csv
Thanks to this post Here and mklement0. I was able to figure out the formatting portion of this problem.
Now I have the remaining code that exports it exactly as intended.
$user= Read-Host -Prompt "What user bonehead?"
$object = Get-ADPrincipalGroupMembership $user
$Table = $object | ForEach-Object {
[pscustomobject] #{
Groups = $_.Name
GroupDesc = (Get-ADGroup $_ -Properties *).Description
GroupOwner = (Get-ADGroup $_ -Properties *).Info
}
}
$Table | Export-csv -NoTypeInformation c:\tmp\test.csv
The -NoTypeInformation helps eliminate the header on the .csv files and the piped Group info through the ForEach-Object cmdlet helped insure every object had it's own row in excel.

Simple PowerShell question - How to convert object into arraylist

I have a variable that contains the following:
Site IP
walmart 10.20.30.40
walmart 10.20.30.41
walmart 10.20.30.42
target 10.20.30.50
target 10.20.30.51
hm 10.20.30.60
This is an object created using New-Object psobject -Property #{ site = <code> ; IP = <code> }. So if I call $obj.site, it does list only the sites and same goes with IP.
I want to convert this is into an arraylist grouped by the sites.
For instance, result output should look like this:
walmart : {10.20.30.40, 10.20.30.41, 10.20.30.42}
target : {10.20.30.50, 10.20.30.51}
hm : {10.20.30.60}
I want to be able to call this from my script like for reasons like adding or subtracting IPs on any site. For instance:
$myArray.target
Which should list the IPs like:
10.20.30.50
10.20.30.51
Instead of the same format - {10.20.30.50, 10.20.30.51}
Then I would add more IPs to these object using the Add method.
($myArray.walmart).Add("10.20.30.44")
The closest I've been able to get is the following (and its output):
$obj | Group-Object -Property Site | Select-Object Name,Group
Name Group
walmart {#{Site=walmart; IP=10.20.30.40}, #{Site=walmart; IP=10.20.30.41}, #{Site=walmart; IP=10.20.30.42}
target {#{Site=target; IP=10.20.30.50}, #{Site=target; IP=10.20.30.51}
hm {#{Site=hm; IP=10.20.30.60}
With that output, I can't call on particular site like $myArray.walmart.
I'm pretty new to powershell, what am I missing? Any help would be much appreciated!
try Something like this:
$Array=#(
[pscustomobject]#{Site='walmart'; ID='10.20.30.40'}
[pscustomobject]#{Site='walmart'; ID='10.20.30.41'}
[pscustomobject]#{Site='walmart'; ID='10.20.30.42'}
[pscustomobject]#{Site='target'; ID='10.20.30.50'}
[pscustomobject]#{Site='target'; ID='10.20.30.51'}
[pscustomobject]#{Site='hm'; ID='10.20.30.60'}
)
$Object=New-Object PSObject
$Array | group Site | %{
#create an new list with all id for the curretn group
$CurrentList = New-Object System.Collections.Generic.List[System.Object]
$_.group.ID | %{$CurrentList.Add($_)}
#Add property and list to the final object
Add-Member Noteproperty -Name $_.Name -value $CurrentList -InputObject $Object
}
#add element to a site
$Object.hm.Add("NewID")

Sort Hashtable with Arrays as values

Description: I'm building a PowerShell-script that searches for files, then gives them unique names, copies them and then verifies them via hash-calculation - I chose to split the script in functions for each step, so it's easier to maintain the whole thing.
To get all values from one function to the other, I chose to use [hashtable]$FooBar - inside $FooBar, there are multiple arrays, such as FullName or OutputPath (which may change per file as they will be copied to subfolders named yyyy-mm-dd). All arrays are correlating with each other (meaning that index 1 contains all values of the first file, index 2 the values for the second file,...) and this works fine as of now.
A short simplified visualisation:
$FooBar = #{}
$FooBar.FullName = #()
$FooBar.Size = #()
$FooBar.Ext = #()
Get-ChildItem | ForEach-Object {
$FooBar.FullName += $_.FullName
$FooBar.Size += $_.Length
$FooBar.Ext += $_.Extension
}
However, I now need to sort them all by one value-set of one of the arrays, e.g. the size. Or, visualised again:
# From:
$FooBar
Name Value
---- -----
fullname {D:\AAA.XYZ, D:\BBB.ZYX, D:\CCC.YZX}
size {222, 111, 555}
extension {.XYZ, .ZYX, .YZX}
# To:
$FooBar = $FooBar | Sort-Object -Property Size -Descending
$FooBar
Name Value
---- -----
fullname {D:\CCC.YZX, D:\AAA.XYZ, D:\BBB.ZYX}
size {555, 222, 111}
extension {.YZX, .XYZ, .ZYX}
I tried $FooBar.GetEnumerator() | Sort-Object -Property Size, but this does not change anything. Google turned up suggestions on how to sort an array of hashtables, but in my case, it's the other way round, and I can't get my head around this because I don't even understand why this is a problem in the first place.
So my question is: is there any way to sort all arrays inside the hashtable by the value-set of one of the arrays? I can't get my head around this.
Disclaimer: I'm a PowerShell-autodidact with no reasonable background in scripting/programming, so it might well be that my "include everything in one hashtable"-solution isn't going to work at all or might be extremely inefficient - if so, please tell me.
The easiest way to go about what I believe you are trying to do is Select-Object
$fooBar = Get-ChildItem | Select-Object FullName, Size, Extension
This will create an array of new objects that only have the desired properties. The reason this works and your method doesn't is because Sort-Object works on properties and the property you are specifying is behind a few layers.
If you need more flexibility than just exact properties, you can create your own like this
$fooBar = Get-ChildItem | Select-Object #{Name = 'SizeMB'; Expression = {$_.Size / 1MB}}
Or manually create new properties with the [PSCustomObject] type accelerator:
$fooBar = Get-ChildItem | ForEach-Object {
[PSCustomObject]#{
FullName = $_.FullName
Extension = $_.Extension
Size = $_.Size
}
}
Update
If you need to add additional properties to the object after it's initially created you have a few options.
Add-Member
The most common method by far is by using the Add-Member cmdlet.
$object | Add-Member -MemberType NoteProperty -Name NewProperty -Value 'MyValue'
$object
Something important to keep in mind is that by default this cmdlet does not return anything. So if you place the above statement at the end of a function and do not separately return the object, your function won't return anything. Make sure you either use the -PassThru parameter (this is also useful for chaining Add-Member commands) or call the variable afterwards (like the example above)
Select-Object
You can select all previous properties when using calculated properties to add members. Keep in mind, because of how Select-Object works, all methods from the source object will not be carried over.
$fooBar | Select-Object *, #{Name = 'NewProperty'; Expression = {'MyValue'}}
psobject.Properties
This one is my personal favorite, but it's restricted to later versions of PowerShell and I haven't actually seen it used by anyone else yet.
$fooBar.psobject.Properties.Add([psnoteproperty]::new('NewProperty', 'MyValue'))
$fooBar
Each member type has it's own constructor. You can also add methods to $fooBar.psobject.Methods or either type to $fooBar.psobject.Members. I like this method because it feels more explicit, and something about adding members with members feels right.
Summary
The method you choose is mostly preference. I would recommend Add-Member if possible because it's the most used, therefore has better readability and more people who can answer questions about it.
I would also like to mention that it's usually best to avoid adding additional members if at all possible. A function's return value should ideally have a reliable form. If someone is using your function and they have to guess when a property or method will exist on your object it becomes very difficult to debug. Obviously this isn't a hard and fast rule, but if you need to add a member you should at least consider if it would be better to refactor instead.
For all practical purposes I'd strongly suggest you just store the objects you need in a single array, sort that once and then reference the individual properties of each object when needed:
$FooBar = Get-ChildItem |Sort-Object -Property Length
# Need the Extension property of the object at index 4?
$FooBar[4].Extension
To answer your actual question:
Array.Sort() has an overload that takes keys and values arrays separately. You could make a copy of the array you want to sort on for each other property you want to sort:
# Create hashtable of correlated arrays
$FooBar = #{}
$FooBar.FullName = #()
$FooBar.Size = #()
$FooBar.Ext = #()
# Types cast explicitly to avoid Array.Sort() calling .CompareTo() on the boxing object
Get-ChildItem | ForEach-Object {
$FooBar.FullName += [string]$_.FullName
$FooBar.Size += [int]$_.Length
$FooBar.Ext += [string]$_.Extension
}
# Define name of reference array property
$SortKey = 'Size'
# Sort all arrays except for the reference array
$FooBar.Keys |Where-Object {$_ -ne $SortKey} |ForEach-Object {
# Copy reference values to new array
$Keys = $FooBar[$SortKey].Clone()
# Sort values in target array based on reference values
[array]::Sort($Keys,$FooBar[$_])
}
# Finally sort the reference array
[array]::Sort($FooBar[$SortOn])
The above only works as long as the reference array is made up of value types
PowerShell makes working with objects ridiculously easy.
Try:
$FooBar = Get-Childitem
$FooBar | Get-Member
This will tell you that $Foobar actually contains objects of FileInfo and DirectoryInfo type, and show you the Properties available.
$FooBarSortedBySizeDesc = $FooBar | Sort-Object Length -Descending
$FooBarFullNamesOnly = $FooBar.FullName

Create an object containing an array of different objects that can grow dynamically in PowerShell

I want to create an object in PowerShell with one of its private members being a list or array of different objects. There are two important features for this request.
The private member list or array must be able to grow dynamically (how would this work syntactically?)
The solution must be backwards compatible with PowerShell 2.0. This means I can't create a custom class using the "class" keyword.
Is there a way this can be done in PowerShell?
are you looking for something like this (works in v2 ):
$x = New-Object psobject
$arr = new-object system.collections.arraylist
$arr.add("test")
$arr.add("test3")
$arr.add(#{"test4"="another1"})
$x | add-member -MemberType NoteProperty -Name arr -Value $arr
$x.arr.add("test5")
write-output $x.arr
by default arrays are polymorphic in powershell , so you can store any type in the array and pass it into the object
private members are not possible in v2 (PowerShell - how to add private members?)
just do it:
#declaration array and variables differents types
$array=#()
$varstring='i am as tring'
$varint=0
$varobject=New-Object -TypeName "pscustomobject" #{val1="12"; val2="test"}
#add elements into array
$array+=$varstring
$array+=$varint
$array+=$varobject
#print array
$array

Powershell: Assign properties to existing array

I have a single dimensional array that I get from either a get-content command or from multi-line text box input. I want to assign a property to the entries in this array, then add more properties to use later in my script.
Something like:
$items = new-object psobject
$items | add-member -membertype NoteProperty –name Name –value NotSet
$items | add-member -membertype NoteProperty –name Percent –value NotSet
$names = #($textboxInputText.Lines)
$names | % { $items | Add-Member noteproperty $_.Name $temp.($_.Name) }
foreach ($item in $items)
{
$percent = {script block}
$item.percent = $percent
}
I know this is broken code, but I wanted to give an example of where I was headed. I've searched far and wide but haven't been able to find exactly what I was looking for.
EDIT:
Code Goal: Get input from a text box or text file (single line entries). Have those entries be assigned to the "name" property, then add a second property to the array (Percent) that will need to be filled in with another block of code.
EDIT 2:
Collection is being used in the following code:
foreach ($item in $collection) {
$psConsoleFile = "PATH TO FILE.pc1"
$variable1 = "something"
$variable2 = "something else"
$command = ".`"Command1 $item.name | Command2 -Switch $variable1 -Switch2 $variable2`""
$OutputScriptBlock = "powershell.exe -PSConsoleFile $psConsoleFile -command $command"
}
The output of this is as follows:
powershell.exe -PSConsoleFile "PATH TO FILE.psc1" -command ."Command1 #{Name=name1; Percentage=}.name | Command2 -Switch1 something"
Why is the code outputting the full row instead of the name?
Also, I'm using PS 4.0 for all implementations of this script.
Ok, I see the problem here. So you have an array of Strings that you got either from a multi-line text box form object, or from a text document with the Get-Content command, but what you really want is an array of PSObjects.
A string object can not have additional properties added to it like you want (well, not conventionally, let's just not go there because you won't be happy with where things end up, trust me on this one). Instead let's take that array of strings, and for each string create a PSObject for it like you want. You will want a ForEach-Object loop for this to be simple. Either way you will want to pipe your input (either the textbox or the get-content command) to a ForEach loop, and you can assign the whole thing to a variable that will collect all of the objects to be worked with later (to update the Percent property). Something like this should accomplish what you want:
[Array]$Collection = $textboxInputText.Lines | ForEach{
New-Object PSObject -Property #{
'Name' = $_
'Percentage' = $null
}
}
I specified $Collection as the type [Array] so that if you wanted to index into it later there wouldn't be any issues should your input only be a single item. Then if you want to update the percentages you can do that by running $Collection through a ForEach loop (either inline or not)
$Collection | ForEach{ $_.Percentage = {Script Block} }
or
ForEach($Item in $Collection){
$Item.Percentage = {Script Block}
}
Now, things to note here... You are not going to be able to just assign $Collection back to your textbox. You could probably assign $Collection.Name, but that may require a newer version of PS since I don't know how backwards compatible that is. If you use a Get-Content command instead of referencing the textbox, simply change $textboxInputText.Lines | ForEach{ to Get-Content "C:\Path\To\File.txt" | ForEach{ and you should be all set.
Edit: Ok, the problem you have now isn't with the object but with how you're trying to expand a property of it within a double quotes. To access the name you would have to create a sub expression within the double quotes by wrapping $Item.Name within $(). So that line for you would look like:
$command = ".`"Command1 $($item.name) | Command2 -Switch $variable1 -Switch2 $variable2`""

Resources