Concatenating non-unique values alongside a unique field in PowerShell - powershell

I have the following set of queries which retrieve a set of data, merge it down so I just get the unique values and add a number to each of them (so I can select that particular item later).
$allMoveRequests = Get-MoveRequest -DomainController server |
select Alias,Status,TargetDatabase,BatchName
$optNum=1
$AllMoveBatches = #($allMoveRequests | Sort-Object -Property BatchName |
Select-Object BatchName,TargetDatabase -Unique) |
Select #{Name="Option";Expression={$optNum;$optNum++}},BatchName,TargetDatabase
$AllMoveBatches | Format-Table -AutoSize | Out-String|% {Write-Host $_}
This returns :
Option BatchName TargetDatabase
------ --------- --------------
1 Batch1 Database1
2 Batch2 Database2
etc. That works as it should, but what I'd like to add is the Status value from those batches, combined where there is more than one rather than creating duplicate entries. For instance, if I simply add Status into the second bit of code I end up with :
Option BatchName TargetDatabase Status
------ --------- -------------- ------
1 Batch1 Database1 Completed
2 Batch1 Database1 In Progress
3 Batch2 Database2 Completed
while what I'd ideally like would be :
Option BatchName TargetDatabase Status
------ --------- -------------- ------
1 Batch1 Database1 Completed,InProgress
2 Batch2 Database2 Completed
I've tried using an expression in the select statement to query all the relevant Status entries and apply -Unique to them, but that just returns all Status entries across all batches, not just those relevant to the current Batch line.
Is there a way to achieve this?

It's not pretty, and it might not be very performant with lots of data, but here's one way to do it...
First, lets create some sample data:
$data = #(
(new-object PSObject -Property ([ordered] #{
"BatchName" = "Batch1"
"TargetDatabase" = "Database1"
"Status" = "Completed"
})),
(new-object PSObject -Property ([ordered] #{
"BatchName" = "Batch1"
"TargetDatabase" = "Database1"
"Status" = "In Progress"
})),
(new-object PSObject -Property ([ordered] #{
"BatchName" = "Batch2"
"TargetDatabase" = "Database2"
"Status" = "Completed"
}))
)
now, process it:
Set-Variable -Name "optNum" -Option AllScope -Value 1
$results = #( $data | group-object BatchName, TargetDatabase ) `
| select-object #{Name="Option";Expression={$optNum; $optNum++}},
#{Name="BatchName";Expression={$_.Group[0].BatchName}},
#{Name="TargetDatabase";Expression={$_.Group[0].TargetDatabase}},
#{Name="Status";Expression={$_.Group.Status -join ", "}} `
| sort-object -Property BatchName
and show the result:
PS> $results
Option BatchName TargetDatabase Status
------ --------- -------------- ------
1 Batch1 Database1 Completed, In Progress
2 Batch2 Database2 Completed
What it's doing is grouping to select the unique combinations of BatchName and DatabaseName and then to make the results it's selecting the BatchName and DatabaseName from the first item in each group, and concatenating all of the Status properties from the items in that group (you could also process the statuses in the Status Expression if you wanted to e.g. sort, filter or de-dupe them within each group).
Note that I've moved your original sort-object BatchName to the end of the pipeline. There's no point sorting, say 1000 objects only to throw half of them away - you might as well sort at the end.
And I could only get your "Option" counter to work by using Set-Variable to make it AllScope as the $optNum++ wasn't incrementing the variable properly when I used $optNum = 1 to initialise it.

mclayton's answer should be the accepted one, but here's a slightly more concise version that uses one of my favorite Powershell idioms: Foreach with a -Begin scriptblock {$i=1} that executes only once.
[pscustomobject]#{BatchName = 'Batch1';TargetDatabase='Database1';Status='Completed'},
[pscustomobject]#{BatchName = 'Batch1';TargetDatabase='Database1';Status='In Progress'},
[pscustomobject]#{BatchName = 'Batch2';TargetDatabase='Database2';Status='Completed'} |
Group BatchName, TargetDatabase |
%{$i=1}{ [pscustomobject]#{Option = $i++
BatchName = $_.Group[0].BatchName
TargetDatabase = $_.Group[0].TargetDatabase
Status = $_.Group.Status -join ','}
}

Related

Convert multiple txt file to one single csv file [duplicate]

When we're trying to export data to other functions via the pipeline, we observe some strange behavior in PowerShell.
Example code:
$Array = #()
$Obj1 = [PSCustomObject]#{
Member1 = 'First'
Member2 = 'Second'
}
$Obj2 = [PSCustomObject]#{
Member1 = 'First'
Member2 = 'Second'
Member3 = 'Third'
}
$Array = $Obj1, $Obj2
$Array | Out-GridView -Title 'Not showing Member3'
$Array = $Obj2, $Obj1
$Array | Out-GridView -Title 'All members correctly displayed'
In the example above you can see that when the first object only contains 2 properties, the Out-GridView CmdLet (and others) only show 2 properties, even though the second object has 3 properties. However, when the first object in the array has 3 properties it does display them all correctly.
Is there a way around this? Because it's not possible to predict up front how many properties on an object there will be and if the object with the most properties will be the first one in the array.
I had the same experience once and created the following reusable 'Union' function:
# 2021-08-25 Removed Union function
Usage:
$Obj1, $Obj2 | Union | Out-GridView -Title 'Showing all members'
It is also supposed to work with complex objects. Some standard cmdlets output multiple object types at once and if you view them (e.g. Out-GridView) or dump them in a file (e.g. Export-Csv) you might miss a lot of properties. Take as another example:
Get-WmiObject -Namespace root/hp/instrumentedBIOS -Class hp_biosSetting | Union | Export-Csv ".\HPBIOS.csv"
Added 2014-09-19:
Maybe this is already between the lines in the comments $Array | Select * | … will not resolve the issue but specifically selecting the properties $Array | Select Member1, Member2, Member3 | … does.
Besides, although in most cases the Union function will work, there are some exceptions to that as it will only align the first object with the rest.
Consider the following object:
$List = #(
New-Object PSObject -Property #{Id = 2}
New-Object PSObject -Property #{Id = 1}
New-Object PSObject -Property #{Id = 3; Name = "Test"}
)
If you Union this object everything appears to be fine and if you e.g. ExportTo-CSV and work with the export .csv file from then on you will never have any issue.
$List | Union
Id Name
-- ----
2
1
3 Test
Still there is a catch as only the first object is aligned. If you e.g. sort the result on Id (Sort Id) or take just the last 2 (Select -Last 2) entries, the Name is not listed because the second object doesn’t contain the Name property:
$List | Union | Sort Id
Id
--
1
2
3
Therefor I have rewritten the Union-Object (Alias Union) function`):
Union-Object
# 2021-08-25 Removed Union-Object function
Syntax:
$Array | Union | Out-GridView -Title 'All members correctly displayed'
Update 2021-08-25
Based on az1d helpful feedback on an error caused by equal property names with different casing, I have created a new UnifyProperties function.
(I will no longer use the name UnionObject for his)
function UnifyProperties {
$Names = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
$InputCollected = #($Input)
$InputCollected.ForEach({
foreach ($Name in $_.psobject.Properties.Name) { $Null = $Names.Add($Name) }
})
$inputCollected | Select-Object #($Names)
}
Usage:
[pscustomobject] #{ one = 1; two = 2; three = 3 },
[pscustomobject] #{ ONE = 10; THREE = 30; FOUR = 4 } |
UnifyProperties
one two three FOUR
--- --- ----- ----
1 2 3
10 30 4
See also: #13906 Add -UnifyProperties parameter to Select-Object

Merge two PSCustomObjects into one- PowerShell

I need help in PowerShell to combine two outputs or two PSCustomObjects into One.
For example,
$services = Get-Service | Select Name, Starttype,Status
$processes = Get-Process | Select ID
I need the output with the table headers
Name, Starttype, Status, ID
I have already tried creating CSV and joining them but the problem is Process ID starts when the entire output ends for the services. I need them to a parallel.
Second I have tried to create PSCustomObjects but no luck.
Please help me with the PowerShell code.
Actual code that I'm trying to achieve.
**$exclusionItems = #()
$OasHighItems = #()
foreach($item in $items){
$exclusionItems += [PSCustomObject]#{
EXCLUSION_BY_NAME_OR_LOCATION = $item.EXCLUSION_BY_NAME_OR_LOCATION
EXCLUSION_EXCLUDE_SUBFOLDERS = $item.EXCLUSION_EXCLUDE_SUBFOLDERS
EXCLUSION_ON_READ= $item.EXCLUSION_ON_READ
}
}
foreach($oas in $oashigh){
$oashighItems += [PSCustomObject]#{
OAS_PROCESSES_LIST = $oas
}
}
$Array = #()
$Array = $exclusionItems,$oashighItems
$Array | Update-FirstObjectProperties | Export-Excel $ExcelParams -TableName Table -Show**
I'm assuming you want to join the two objects by their names, i.e. match the Process-Name with the Service-Name. For this you can loop over all processes & services, keep only those where service-name equals process-name, and use a calculated property to merge the result into one object:
$services = Get-Service;
Get-Process | ForEach-Object {$p = $_; $services |
Where-Object{$p.ProcessName -eq $_.Name} |
Select-Object Name,StartType,Status,#{n='ID';e={$p.ID}}}
The output on my machine is:
Name StartType Status ID
---- --------- ------ --
CcmExec Automatic Running 14856
CmRcService Automatic Running 5748
FusionInventory-Agent Automatic Running 5996
IBMPMSVC Automatic Running 3540
IntelAudioService Automatic Running 6104
... and so on ...

Create Union of Multiple Tables in Powershell

I'm trying to create a union of multiple tables in Powershell to output in a user-friendly format as a report, similar to a UNION query in SQL.
I have the following code:
$ft = #{auto=$true; Property=#("MachineName", "Status", "Name", "DisplayName")}
$hosts = #("svr001", "svr002")
$report = #()
ForEach ($h in $hosts) {
$results = Get-Service -CN $h -Name MyService
$report += $results | Format-Table #ft
# Other things occur here, which is why I'm creating $report for output later.
}
Write-Output $report
The output of this code is as follows:
MachineName Status Name DisplayName
----------- ------ ---- -----------
svr001 Running MyService MyServiceDisplayName
MachineName Status Name DisplayName
----------- ------ ---- -----------
svr002 Running MyService MyServiceDisplayName
Since you simply add arrays to do a union in Powershell (i.e.,
$union = #(0, 1, 2) + #(3, 4, 5)), my initial thought was that I should
get the following output:
MachineName Status Name DisplayName
----------- ------ ---- -----------
svr001 Running MyService MyServiceDisplayName
svr002 Running MyService MyServiceDisplayName
In retrospect, I think I understand why I do not get this output, but I'm
unclear how to create a union of the two tables from the first output example into a single table as in the second, and I haven't been able to locate anything in the docs or examples online that would send me in the right direction.
Move the Format-Table to the last command. Format-* cmdlets create special format-objects that you can't work with manually so theres no point in saving them. When you save the result of Format-* to an array, you're saving the "report" which is why you get two tables in the output (array consists of two reports).
Collect the data first, then use Format-Table when you want to display the results.
$ft = #{auto=$true; Property=#("MachineName", "Status", "Name", "DisplayName")}
$hosts = #("svr001", "svr002")
$report = #()
ForEach ($h in $hosts) {
$results = Get-Service -ComputerName $h -Name MyService
$report += $results
# Other things occur here, which is why I'm creating $report for output later.
}
#Write-Output is not necessary as it is default behaviour
$report | Format-Table #ft
Sample output (used wuauserv as servicename):
MachineName Status Name DisplayName
----------- ------ ---- -----------
localhost Stopped wuauserv Windows Update
frode-pc Stopped wuauserv Windows Update
The Get-Service Cmdlet also supports an array of strings for the -ComputerName parameter. This works for me:
Get-Service -CN $hosts -Name MyService | Format-Table #ft
Sample Output using wuauserv:
MachineName Status Name DisplayName
----------- ------ ---- -----------
Tim-SRV1 Running wuauserv Windows Update
Tim-SRV2 Stopped wuauserv Windows Update

How to access list value in Get-EC2Instance's RunningInstance method?

I'm trying to retrieve the instanceid, public dns name, and "Name" tag from the object returned by get-ec2instance.
$instances = foreach($i in (get-ec2instance)) '
{ $i.RunningInstance | Select-Object InstanceId, PublicDnsName, Tag }
Here's the output:
InstanceId PublicDnsName Tag
---------- ------------- ---
myInstanceIdHere myPublicDnsName {Name}
... ... {Name}
I would like to be able to access {Name} using the line of code above and print its value in this output. I've done a little bit of research since this initial posting, and found...
PS C:\Users\aneace\Documents> $instances[0].Tag.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
Between this and the AWS docs, I think Tag refers to this list, but I'm not certain. I can access a table that prints key and value columns by calling $instances[0].Tag, but my problem now is that I would like that Value to be the output to my first table instead of the {Name} object. Any suggestions?
Per the documentation, the Tag property is a list of Tag objects. So in general, there will be multiple keys/values stored there. Are you assuming that in your case there is only 1?
Select-Object allows you to grab not just raw property values, but calculated values as well. Let's say you just want a comma-delimited list of the Values from the Tag objects in the list. Here's how you would do it:
$instances = Get-EC2Instance `
|%{ $_.RunningInstance } `
| Select-Object InstanceId,PublicDnsName,#{Name='TagValues'; Expression={($_.Tag |%{ $_.Value }) -join ','}}
The elements of $instances will now have a property TagValues which is a string consisting of the Value from all Tags associated with the instance.
Here is how to extract tags into a flat object along with other properties
$region = 'us-west-2'
$instances = (Get-Ec2Instance -Region $region).Instances | select `
#{Name="ServerName";Expression={$_.tags | where key -eq "Name" | select Value -expand Value}},`
InstanceType ,`
InstanceId,`
ImageId,`
#{Name="Role";Expression={$_.tags | where key -eq "Role" | select Value -expand Value}},`
#{Name="Group";Expression={$_.tags | where key -eq "Group" | select Value -expand Value}},`
#{Name="Subsystem";Expression={$_.tags | where key -eq "subsystem" | select Value -expand Value}},`
#{Name="State";Expression={$_.State.Name}},`
#{Name="Region";Expression={$region}}
$instances | Sort-Object -Property State, ServerName | Format-Table

How do I add a column of incrementing values to cmdlet output?

Suppose I call Get-Service and want to assign a new column ID with the cmdlet output that prints incrementing integers so that:
ID Status Name DisplayName
-- ------ ---- -----------
0 Running AdobeARMservice Adobe Acrobat Update Service
1 Stopped AeLookupSvc Application Experience
2 Stopped ALG Application Layer Gateway Service
I'm trying to use Select-Object right now to add this column, but I don't quite understand how to iterate a variable in this sort of expression. Here's what I've got:
Get-Service |
Select-Object #{ Name = "ID" ; Expression= { } }, Status, Name, DisplayName |
Format-Table -Autosize
Is there a way to iterate integers within Expression= { }, or am I going about this problem the wrong way?
You can do it this way, though you will need to maintain some counter variable outside of the main expression.
$counter = 0
Get-Service |
Select-Object #{ Name = "ID" ; Expression= {$global:counter; $global:counter++} }, Status, Name, DisplayName |
Format-Table -Autosize
Another option, which is perhaps cleaner
Get-Service `
|% {$counter = -1} {$counter++; $_ | Add-Member -Name ID -Value $counter -MemberType NoteProperty -PassThru} `
| Format-Table ID
I asked the same question a different way and got the following answer
$x = 10
Get-Service |
Select-Object #{ Name = "ID" ; Expression={ (([ref]$x).Value++) }}, Status, Name, DisplayName | Format-Table -Autosize
It wasn't at all clear to me that the expression is being invoked within Select-Object's scope, not the pipe's. The [ref] qualifier bumps the increment's result up to the pipe's scope achieving the same result as explicitly specifying the variable as global.