Array does not wipe - powershell

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?

Related

How to get the name of a PsCustomObject?

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
}
}

How to display all properties with Format-Table cmdlet

I have few [pscustomobject] objects that can have not all properties.
For example:
PS> $1 = [pscustomobject]#{ A='a1'; B='b1' }
PS> $2 = [pscustomobject]#{ A='a2'; C='c2' }
And I try to display all properties with Format-Table like this:
PS> $1,$2 | Format-Table
A B
- -
a1 b1
a2
PS> $2,$1 | Format-Table
A C
- -
a2 c2
a1
But every time it displays only properties from first object in collection.
I want to display all properties like if I set -Property argument explicitly.
PS> $1,$2 | Format-Table -Property A,B,C
A B C
- - -
a1 b1
a2 c2
Setting -Property argument is good if:
All set of properties is known in advance
Collection is small and I can get all properties with Get-Member -MemberType Properties
But I have a huge collection (above 10000 objects) with unknown properties so I need help with it.
REMARK: Format-Table will be used only for small slices (10-100 elements).
For that, you can use below function to merge all properties into the first object:
function Complete-ObjectHeaders {
# function to add properties to the first item in a collection of PSObjects
# when this object is missing properties from items further down the array.
# you may need this if you have such a collection and want to export it
# to Csv, since Export-Csv (and also Format-Table) only looks at the FIRST
# item to create the csv column headers.
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[PSObject[]]$Collection,
[int]$MaxItemsToTest = -1, # < 0 --> test all items in the collection
[switch]$SortHeaders
)
# Try and find all headers by looping over the items in the collection.
# The headers will be captured in the order in which they are found.
if ($MaxItemsToTest -gt 0) {
$MaxItemsToTest = [math]::Min($MaxItemsToTest, $Collection.Count)
$headers = for($i = 0; $i -lt $MaxItemsToTest; $i++) {
($Collection[$i].PSObject.Properties).Name
}
$headers = $headers | Select-Object -Unique
}
else {
$headers = $Collection | ForEach-Object {($_.PSObject.Properties).Name} | Select-Object -Unique
}
if ($SortHeaders) { $headers = $headers | Sort-Object }
# update the first object in the collection to contain all headers
$Collection[0] = $Collection[0] | Select-Object $headers
,$Collection
}
Use like this:
$1 = [pscustomobject]#{ A='a1'; B='b1' }
$2 = [pscustomobject]#{ A='a2'; C='c2' }
# just output to console
Complete-ObjectHeaders -Collection $1,$2 | Format-Table -AutoSize
# or capture the merged array of objects in a new variable you can save as CSV file for instance
$merged = Complete-ObjectHeaders -Collection $1,$2
$merged | Export-Csv -Path 'D:\Test\Merged.csv' -NoTypeInformation
Output:
A B C
- - -
a1 b1
a2 c2
Thanks #theo for the answer.
I used it to write my own version of a function that supports pipelining.
function Expand-Properties {
[Cmdletbinding()]
param(
[Parameter(ValueFromPipeline)]
$InputObject,
[Parameter()]
[Alias('All')]
[switch]
$ExpandAll,
[Parameter()]
[switch]
$SortHeaders
)
begin {
$collection = [System.Collections.ArrayList]::new()
$properties = [System.Collections.ArrayList]::new()
}
process {
[void]$collection.Add($InputObject)
$properties.AddRange((($InputObject.PSObject.Properties).Name))
}
end {
if ($SortHeaders) {
$properties = $properties | Sort-Object -Unique
} else {
$properties = $properties | Select-Object -Unique
}
if ($ExpandAll) {
for ($i = 0; $i -lt $collection.Count; ++$i) {
$collection[$i] = $collection[$i] | Select-Object -Property $properties
}
} else {
$collection[0] = $collection[0] | Select-Object -Property $properties
}
$collection
}
}
EXAMPLE:
PS> $1 = [pscustomobject]#{ A='a1'; B='b1' }
PS> $2 = [pscustomobject]#{ A='a2'; C='c2' }
PS> $1, $2 | Expand-Properties
A B C
- - -
a1 b1
a2 c2

Powershell - Where-Object in forEach Loop

I need some help for executing a little script and filtering results ...
I checkmy VM diskspace with the following script
Get-VM | ForEach-Object {
$VM = $_
$_.Guest.Disks | ForEach-Object {
$Report = "" | Select-Object -Property VM,Path,Capacity,FreeSpace,PercentageFreeSpace
$Report.VM = $VM.Name
$Report.Path = $_.Path
$Report.Capacity = $_.Capacity
$Report.FreeSpace = $_.FreeSpace
if ($_.Capacity) {$Report.PercentageFreeSpace = [math]::Round(100*($_.FreeSpace/$_.Capacity))}
$report
}
}
But I'd like to add a filter that my report only show me PercentageFreeSPace lesser than 20.
I try to add a where-object condition to my report with no success...
Can somebody help me please ? I m a beginner in PS...
Thanks in advance,
Best regards
For future questions you should format your script to be more readable.
I believe I have done it quite well below:
[EDIT] Seems that something gone wrong with displaying your question on my laptop. Just after my answer, the formatting is almost the same as provided below.
Get-VM | ForEach-Object {
$VM = $_
$_.Guest.Disks | ForEach-Object {
$Report = "" | Select-Object -Property VM,Path,Capacity,FreeSpace,PercentageFreeSpace $report
$Report.VM = $VM.Name
$Report.Path = $_.Path
$Report.Capacity = $_.Capacity
$Report.FreeSpace = $_.FreeSpace
if ($_.Capacity) {
$Report.PercentageFreeSpace = [math]::Round(100*($_.FreeSpace/$_.Capacity))
}
$report
}
}
To be honest I do not understand why you are using pipe at line 4.
Regarding your question, you should puy Where-Object clause before you will go to second for each loop.
First off, try and avoid multiple nested pipes to foreach-object, use foreach instead. That is, don't do
Get-VM | ForEach-Object {
$VM = $_
$_.Guest.Disks | ForEach-Object {
but
foreach($vm in Get-VM) {
foreach($disk in $vm.Guest.disks) {
This makes it easy to see what objects are handled later, and there isn't need to save current object on the pipeline into a temp variable (which you do by $VM = $_).
Also, objects can be initialized via a hash table instead of using select-object. Like so,
$pctLimit = 20 # Variable sets percentage limit instead of a magic number
foreach($vm in Get-VM) {
foreach($disk in $vm.Guest.Disks) {
# Report object is created via hash table
$Report = new-object PSObject -Property #{
VM = $VM.Name
Path = $disk.Path
Capacity = $disk.Capacity
FreeSpace = $disk.FreeSpace
ZeroDisk = ($disk.Capacity -gt 0) # NB: calculated property
}
if (-not $Report.ZeroDisk) { # Process only non-zero disks
$Report.PercentageFreeSpace = [math]::Round(100*($Report.FreeSpace/$Report.Capacity))
if($Report.PercentageFreeSpace -lt $pctLimit) {
$report
} else {
# Disk had more than $pctLimit free space. Now what?
}
} else {
# Disk had zero capacity. Now what?
}
}
}
FIrst of all thanks for your help
I manage to filter with the following
Get-VM | ForEach-Object {
$VM = $_
$_.Guest.Disks | ForEach-Object {
$Report = "" | Select-Object -Property VM,Path,Capacity,FreeSpace,PercentageFreeSpace
$Report.VM = $VM.Name
$Report.Path = $_.Path
$Report.Capacity = $_.Capacity
$Report.FreeSpace = $_.FreeSpace
if ($_.Capacity) {$Report.PercentageFreeSpace = [math]::Round(100*($_.FreeSpace/$_.Capacity))}
if( $_.Capacity -and $Report.PercentageFreeSpace -lt 30 ) {
$Report
}
}
}
`
Thanks,
Best regards

How to get a template list from several clusters?

I'm trying to migrate from a 6.0 vCenter to a 6.5 vCenter, and want to migrate all templates. How can I select all clusters at once to retrieve the full template list?
I have a lot of templates in my 6.0 vCenter and need to export a list to migrate them all at once in my new 6.5 vCenter, using Powercli. The only way I found is by using a foreach loop, in which I must provide a cluster name.
I tried using "get-datacenter" instead of "get-cluster" but the result is even worse.
$toto = foreach ($vmhost in Get-Cluster 'my_cluster'|Get-VMHost) {
Get-Template -Location $vmhost |
select name, #{n='VMHOST';e={$vmhost.name}},
#{n='VMTX';e={$_.extensiondata.config.files.VmPathName}}
}
$toto | Export-Csv C:\scripts\Templates.csv
The code works but doesn't show me all templates in the vCenter.
How can I make it work so that I can have all templates in all clusters at once, without using a loop for each?
The function extracts all templates from a single datacenter target by scanning all available clusters and hosts within; dumping the complete result into an $array_list variable.
Function Render_Template_List {
Clear-Host
$array_list = [System.Collections.ArrayList]#()
if (!($global:templates_bool)) {
$host.ui.RawUI.WindowTitle = "Retrieving available templates for use with '$global:datacenter' datacenter...Please Wait!"
if ($global:datacenter -eq 'none'){
$ESX = get-datacenter | get-VMhost | %{$_.Extensiondata.MoRef}
} else {
$ESX = get-datacenter -name "$global:datacenter" | get-VMhost | %{$_.Extensiondata.MoRef}
}
If ($global:Template -eq 'none') {
$Query = Get-Template | where {$ESX -contains $_.Extensiondata.Runtime.Host} | Sort-Object
} else {
$Query = Get-Template -name "$global:Template" | where {$ESX -contains $_.Extensiondata.Runtime.Host} | Sort-Object
}
$global:templates_bool = $true
$global:templates_query = $query
}
$seperator = "{,}"
$query = 0
$arr = 1
foreach ($template in $global:templates_query ) {
if ($arr -eq 1) {
foreach ($array in $global:templates_array) {
If (!($array -like "*#*")) {
$query = $query + 1
$val_template = $array.split($seperator)[2]
$val_template = $val_template.replace("`"","")
if ("$val_template" -eq "$template") {
$val_datacenter = $array.split($seperator)[1]
$val_datacenter = $val_datacenter.replace("`"","")
if ("$val_datacenter" -eq "$global:datacenter") {
$array_list.Add("$val_template") | Out-Null
}
}
}
}
}
if ($query -eq 0) {
$array_list.Add("$template") | Out-Null
$arr = 0
}
}
return $array_list
}

compare two csv using powershell and return matching and non-matching values

I have two csv files, i want to check the users in username.csv matches with userdata.csv copy
to output.csv. If it does not match return the name alone in the output.csv
For Ex: User Data contains 3 columns
UserName,column1,column2
Hari,abc,123
Raj,bca,789
Max,ghi,123
Arul,987,thr
Prasad,bxa,324
username.csv contains usernames
Hari
Rajesh
Output.csv should contain
Hari,abc,123
Rajesh,NA,NA
How to achieve this. Thanks
Sorry for that.
$Path = "C:\PowerShell"
$UserList = Import-Csv -Path "$($path)\UserName.csv"
$UserData = Import-Csv -Path "$($path)\UserData.csv"
foreach ($User in $UserList)
{
ForEach ($Data in $UserData)
{
If($User.Username -eq $Data.UserName)
{
# Process the data
$Data
}
}
}
This returns only matching values. I also need to add the non-matching values in output
file. Thanks.
something like this will work:
$Path = "C:\PowerShell"
$UserList = Import-Csv -Path "$($path)\UserName.csv"
$UserData = Import-Csv -Path "$($path)\UserData.csv"
$UserOutput = #()
ForEach ($name in $UserList)
{
$userMatch = $UserData | where {$_.UserName -eq $name.usernames}
If($userMatch)
{
# Process the data
$UserOutput += New-Object PsObject -Property #{UserName =$name.usernames;column1 =$userMatch.column1;column2 =$userMatch.column2}
}
else
{
$UserOutput += New-Object PsObject -Property #{UserName =$name.usernames;column1 ="NA";column2 ="NA"}
}
}
$UserOutput | ft
It loops through each name in the user list. Line 9 does a search of the userdata CSV for a matching user name if it finds it it adds the user data for that user to the output if no match is found it adds the user name to the output with NA in both columns.
had to change your userList csv:
usernames
Hari
Rajesh
expected output:
UserName column1 column2
-------- ------- -------
Hari abc 123
Rajesh NA NA
I had a similar situation, where I needed a "changed record collection" holding the entire record when the current record was either new or had any changes when compared to the previous record. This was my code:
# get current and previous CSV
$current = Import-Csv -Path $current_file
$previous = Import-Csv -Path $previous_file
# collection with new or changed records
$deltaCollection = New-Object Collections.Generic.List[System.Object]
:forEachCurrent foreach ($row in $current) {
$previousRecord = $previous.Where( { $_.Id -eq $row.Id } )
$hasPreviousRecord = ($null -ne $previousRecord -and $previousRecord.Count -eq 1)
if ($hasPreviousRecord -eq $false) {
$deltaCollection.Add($current)
continue forEachCurrent
}
# check if value of any property is changed when compared to the previous
:forEachCurrentProperty foreach ($property in $current.PSObject.Properties) {
$columnName = $property.Name
$currentValue = if ($null -eq $property.Value) { "" } else { $property.Value }
$previousValue = if ($hasPreviousRecord) { $previousRecord[0]."$columnName" } else { "" }
if ($currentValue -ne $previousValue -or $hasPreviousRecord -eq $false) {
$deltaCollection.Add($currentCenter)
continue forEachCurrentProperty
}
}
}