Remove one or many members from Object in powershell - arrays

I have created a custom object called $info and moving it to an array $arr ,
How is it possible to remove one member along with its all properties ?
My script:
Get-Process | ForEach-Object{
$info = New-Object -TypeName PSObject
$info | Add-Member -Type NoteProperty -Name Process -Value $_.processname
$info | Add-Member -Type NoteProperty -Name ID -Value $_.id
$arr += $info
}
$arr | ft -AutoSize
The result looks like this :
Process ID
------- --
ApplicationFrameHost 38556
AppVShNotify 9792
armsvc 2336
atieclxx 6944
atiesrxx 1844
audiodg 59432
CcmExec 3988
chrome 46068
How can I remove one particular member for example "audiodg 59432" gets removed
audiodg 59432

Your terminology is a bit incorrect here. A member is on an individual object. When you use Add-Member above you're adding properties to each individual object, then you're returning an array of objects.
You're asking how to remove an individual object from the array.
In PowerShell you cannot remove an item from an array. You could instead filter the array based on some criteria and create a new one:
$newArr = $arr | Where-Object { $_.Name -ne 'audiodg' }
# or
$newArr = $arr | Where-Object { $_.ID -ne 59432 }

Related

Adding array elements to a PSObject

Preface: I haven't any formal training with script writing. So I'm sure many of you will be banging you head against the table wondering what I'm doing.
I am working to gather information on our current catalog of Teams sites, and respective site owners. I have a PS script to gather the list of sites, then it loops through each site to get list of owners. After I gather that owner list (stored in a var) I loop through that var to get the user name and store each user name in an array as a new element. That element is then used later when adding a new member to a PSObject so I can export the results to a CSV. Here's a bit of the code;
#Var's
$Count = 1
$TSO = New-Object PSObject
$SiteOwner = #("SiteOwner0","SiteOwner1","SiteOwner2","SiteOwner3","SiteOwner4","SiteOwner5","SiteOwner6","SiteOwner7") #Array to create column headers in the CSV
#STEP 1: Get a list of the sites
$Sites = get-team | sort-object DisplayName
#STEP 2: Get a list of the owners of the different sites
FOREACH ($i in $Sites){
$h = Get-TeamUser -GroupID $i.GroupID | Where {$_.role -eq "Owner"}
$Count += 1
Write-Host . -ForeGroundColor "Cyan" -NoNewLine
#Add the new columns to $TSO
$TSO | Add-Member -MemberType NoteProperty -Name "SiteName" -Value $i.DisplayName -Force
$TSO | Add-Member -MemberType NoteProperty -Name "GroupOwner" -Value $i.Description -Force
$TSO | Add-Member -MemberType NoteProperty -Name "Visibility" -Value $i.Visibility -Force
$TSO | Add-Member -MemberType NoteProperty -Name "Archived" -Value $i.Archived -Force
$TSO | Add-Member -MemberType NoteProperty -Name "SiteEmail" -Value $i.MailNickname -Force
#loop through each member to discover the assigned role
FOREACH ($o in $h){
Write-Host . -ForeGroundColor "Cyan" -NoNewLine
#Build the dynamics of the owners before passing to $TSO
$OHolder += $o.user
} #END NESTED FOREACH
#Handle BLANK elements in the $OHolder array
#The number 8 is the number of array elements we need a value for
$Comp = 0
While($Comp -lt 8){
If($OHolder[$Comp] -ne $null){
$TSO | Add-Member -MemberType NoteProperty -Name $SiteOwner[$Comp] -Value $OHolder[$Comp] -Force
}
ELSEIF(-not ($OHolder[$Comp])){
$OHolder[$Comp] = "NA"
$TSO | Add-Member -MemberType NoteProperty -Name $SiteOwner[$Comp] -Value $OHolder[$Comp] -Force
}
#Increment the $Comp
$Comp += 1
}#END WHILE
#Resetting the $Comp outside the loop
$Comp = 0
#************* END STEP 2 *************************
#Squirt out the info to a CSV
$TSO | Export-CSV -Path $OPathOut -NoTypeInformation -Append -Force
#Reset the $OHOLDER Array
$OHolder = #()
} #END FOREACH
My problem is this; When adding the site owner to the PSObject for siteowner ($TSO | Add-Member -MemberType NoteProperty -Name $SiteOwner[$Comp] -Value $OHolder[$Comp] -Force) it's counting each character of the value as an element.
For example: $OHolder will have two elements, $OHolder[0] is supposed to equal "doe#domain.com" and $OHolder[1] is suppose to equal "john#domain.com". What actually happens is the length of the array becomes 29 (for each character) rather than 2.
How can I add the users to the PSObject as intended? Right now the output will have:
SiteOwner0 | SiteOwner1 | SiteOwner2
d | o | e
# |d |o
Do not use += to add items to an array. It is inefficient if the arrays become large. A new array is created each time after the current array contents are read into memory. You can simply output each item inside of the foreach and assign the array variable to the foreach output.
$OHolder = #(FOREACH ($o in $h){
Write-Host . -ForeGroundColor "Cyan" -NoNewLine
#Build the dynamics of the owners before passing to $TSO
$o.user # Output
})
In the simplistic example above, a loop is likely not even necessary as $OHolder = ,$h.user should suffice in PowerShell v3+.
You are not defining $OHolder variable before using += operator.
By default powershell seems to define a string variable in this case.
$undefVar = $null
$undefVar += "test"
$undefVar += "some"
# content of $undefVar is now testsome
Try to move $OHolder = #() before the loop FOREACH ($o in $h){

How to add a custom property to a PowerShell array?

Say I have a PowerShell array $Sessions = #() which I am going to fill with PSCustomObjects. How can I add a custom property to the array itself? E.g. so I can have $Sessions.Count which is built-in and $Sessions.Active which I want to set to the active session count.
I know that I can add properties to PSCustomObjects (in a dirty way) with
$MyCustomObject = "" | Select-Object Machine, UserName, SessionTime
but though doing so on an array would not result in the property being added.
So how can I achieve my goal? Is there any way to create a custom array?
The answer to your question as stated would be to just use Add-Member on the array object.
Add-Member -InputObject $sessions -MemberType NoteProperty -Name "State" -Value "Fabulous"
Adding a property to each element after you created the object is similar.
$sessions | ForEach-Object{
$_ | Add-Member -MemberType NoteProperty -Name "State" -Value "Fabulous"
}
This of course comes with a warning (that I forgot about). From comments
Beware, though, that appending to that array ($sessions += ...) will replace the array, thus removing the additional property.
Ansgar Wiechers
Depending on your use case there are other options to get you want you want. You can save array elements into distinct variables:
# Check the current object state
$state = $object.Property .....
# Add to the appropriate array.
if($state -eq "Active"){
$activeSessions += $object
} else {
$inactiveSessions += $object
}
Or you could still store your state property and post process with Where-Object as required:
# Process each inactive session
$sessions | Where-Object{$_.State -eq "Active"} | ForEach-Object{}
To avoid the destroying / recreating array issue, which can be a performance hog, you could also use an array list instead.
$myArray = New-Object System.Collections.ArrayList
Add-Member -InputObject $myArray -MemberType ScriptMethod -Name "NeverTellMeTheOdds" -Value {
$this | Where-Object{$_ % 2 -ne 0}
}
$myArray.AddRange(1..10)
$myArray.NeverTellMeTheOdds()
Notice that the array had its member added then we added its elements.
As Matt commented, you can use the Add-Member on an enumerable type by supplying it as a positional argument to the -InputObject parameter.
To allow for resizing after adding the new property, use a generic List instead of #():
$list = [System.Collections.Generic.List[psobject]]::new()
$list.AddRange(#(
[pscustomobject]#{SessionId = 1; Active = $true}
[pscustomobject]#{SessionId = 2; Active = $false}
[pscustomobject]#{SessionId = 3; Active = $true}
) -as [psobject[]])
Add-Member -InputObject $list -MemberType ScriptProperty -Name ActiveSessionCount -Value {
return #($this |? Active -eq $true).Count
}
Now you can retrieve the active session count easily:
PS C:\> $list.ActiveSessionCount
2

How to match a value with my custom-object array

I'm having dificulties working with an array of custom objects and matching it with a variable.
I have a variable: $CmbCust.SelectedItem (currently selected item in a WPF form)
Custom-Object and the creation of my items in the combobox:
$CustomerFileArray = #()
ForEach ($c in (Get-ChildItem $ProgramRoot\Customers -Filter Customer-*.xml | sort Name -descending)) {
$XmlCustomer = [xml](Get-Content $ProgramRoot\Customers\$c)
if ($XmlCustomer.Office365.Customer.Name -eq "") {
$CustomerName = "- Geen naam"
}
Else {
$CustomerName = $XmlCustomer.Office365.Customer.Name
}
$CustomerItem = New-Object PSObject
$CustomerItem | Add-Member -type NoteProperty -Name 'Name' -Value $CustomerName
$CustomerItem | Add-Member -type NoteProperty -Name 'File' -Value $c
$CustomerFileArray += $CustomerItem
[void] $CmbCust.Items.Add($CustomerName)
}
$CmbCust.SelectedItem = $XmlOffice365.Office365.Customer.Name
My question is, how can I match the value in $CmbCust.SelectedItem with my Array $CustomerFileArray's File property
The action I would like to do is to get a path of the selected item to remove it. I've Googled and came up with:
$RemoveFile = #()
$RemoveFile | where {$CustomerFileArray.ContainsKey($_.CmbCust.SelectedItem)}
Remove-Item $ProgramRoot\Customers\$RemoveFile -Force
But that doesn't seem to work...
Thanks in advance!
Since the SelectedItem property contains a string found in the Name property of one of the items in $CustomerFileArray, you should apply Where-Object to $CustomerFileArray like so:
$CustomerFileArray |Where-Object {$_.Name -eq $CmbCust.SelectedItem} |Select-Object -ExpandProperty File
This will return the FileInfo object you originally assigned to the File property of the corresponding object in the array

Output of array of arrays in Powershell / Powercli gets truncated w/ellipses

The purpose of my script is to get the entire list of VMs from our vcenter, check to see if they have DNS entries - if the dns entry is null then output the VM name and corresponding IP address. Pretty simple, but I'm getting tripped up combining the VM name array with the IP address array. Here's what I mean:
$mainArray = #()
$vmArray = #()
$ipArray = #()
$vms = Get-VM | select Name, #{N="DnsName"; E={$_.ExtensionData.Guest.Hostname}}, #{N="IPAddress"; E={$_.Guest.IPAddress[0]}}
foreach ($vm in $vms)
{
$dnsname = $vm.DnsName
$ipaddr = $vm.IPAddress
$vmname = $vm.Name
if (!$vm.DNSName) #if DNS name is null
{
$vmArray += $vmname
$ipArray += $ipaddr
}
}
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'VM Name' -Value $vmArray
$item | Add-Member -type NoteProperty -Name 'IP Address' -Value $ipArray
$mainArray += $item
$mainArray | ft -wrap -autosize
This outputs the two columns next to each other, but the output is separated by commas and is truncated with an ellipse. I know that there's a better way to do this, so I'm open to suggestions. Also be curious to know how to make this work just for my own understanding. Appreciate the help everyone.
Your main issue is that you are populating an array outside your loop. I don't have access to PowerCLI right in front of me now but we should be able to remove most of that code as it appears to be redundant and needless. Continuing after $vms being declared
$vms | Where-Object{!$_.DNSName} | ForEach-Object{
New-Object PSObject -Property #{
'VM Name' = $_.Name
'IP Address' = $_.IPAddress
}
} | ft -wrap -autosize
Before you made a two arrays. One for names and the other ips. Then you took those 2 arrays and made a single object with those two properties. I removed the need for the if statement with the Where-Object{!$_.DNSName}. Now all the objects being processed are the ones you want.
You could really do this is in one/two lines using the same logic.
Get-VM | Select #{N='VM Name';E={$_.Name}}, #{N="DnsName"; E={$_.ExtensionData.Guest.Hostname}}, #{N="IP Address"; E={$_.Guest.IPAddress[0]}} |
Where-Object{!$_.DNSName} | Select "VM Name","IP Address" | Format-Table -wrap -autosize
Again, I cannot test but it should work. If there are any errors they should be easily correctable. Assuming I figured out your issue in the first place.
About the ellipses
PowerShell has a variable $FormatEnumerationLimit that governs how many array elements are display in console output. There is a good article that discusses it on TechNet
By default $FormatEnumerationLimit is set to 4
Matt, I had found that link earlier but didn't find it very helpful since it makes the output garbled and doesn't breakout the values by row. This is how I was able to make the original script work:
$i=0
write-output $vmArray |
foreach {
new-object psobject -property #{
VMName = $_
IPAddress = $ipArray[$i++]
}
} | ft -auto -wrap
Rather unconventional, but does work nonetheless. There really should be an easier way to combine array side by side, but I digress. Thanks a ton Matt.
Update: This is probably more conventional:
For($i=0;$i -lt $vmArray.Count;$i++)
{
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'VM Name' -Value $vmArray[$i]
$item | Add-Member -type NoteProperty -Name 'IP Address' -Value $ipArray[$i]
$mainArray += $item
}
$mainArray | ft -wrap -autosize

Powershell add-member. Add a member that's an ArrayList?

The Powershell "add-member" command is very useful. I use it to add properties to custom objects. Sometimes I set a member as an array to hold multiple objects. Is it possible to add an ArrayList as a member on a custom object?
Imagine a list of articles has properties "index", "title", and "keywords." In Powershell, you could put this code in a loop:
for($i = 0; $i -lt 100; $i++) {
$a = new-object -TypeName PSObject
$a | Add-Member -MemberType NoteProperty -Name index -Value $i
$a | Add-Member -MemberType NoteProperty -Name title -Value "Article $i"
$a | Add-Member -MemberType NoteProperty -Name keywords -Value #()
$articles += $a
}
You'd end up with an array, $articles, of Article objects, each with members index, title, and keywords. Furthermore, the keywords member is an array that can have multiple entries:
$articles[2].keywords += "Stack Exchange", "Powershell", "ArrayLists"
$articles[2].keywords[2]
Powershell
This meets most of my needs, but I just don't like dealing with arrays. ArrayLists are just easier to work with, if only because
$arrayList1.remove("value")
is so much more intuitive than
$array1 = $array1 |? {$_ new "value"}
Is there a way with Add-Member to add an ArrayList as a member? Or am I stuck with arrays? If Powershell doesn't support thi snatively, could I pop in some C# code to make a new class with an ArrayList as a member?
$arr = #("one","two","three")
$arr.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
$a = new-object -TypeName PSObject
[System.Collections.ArrayList]$arrList=$arr
$a | Add-Member -MemberType NoteProperty -Name ArrayList -value $arrlist
$a.ArrayList
one
two
three
$a.ArrayList.remove("one")
$a.ArrayList
two
three
To add a blank ArrayList to your custom object just use
$a | Add-Member -MemberType NoteProperty -Name ArrayList -value (New-object System.Collections.Arraylist)
I find all this Add-Member stuff to be confusing for data only. In powershell 3, you can just make an object from a hashtable, and use a little thing I learned from a blog about using Invoke to get a Collection typed object:
$myObject = [PSCustomObject]#{
index = $idx;
title = $title;
keywords = {#()}.Invoke()
}
$myObject.keywords.Add("foo")
$myObject.keywords.Add("bar")
Write-Host "Original"
$myObject.keywords
Write-Host
Write-Host "New:"
[void]$myObject.keywords.Remove("foo")
$myObject.keywords
Write-Host
Original
foo
bar
New:
bar

Resources