PowerShell: Unique Array of Objects - powershell

I've got an array of objects for team names and emails as well as the tickets associated to that team in a seperate array however its not working quite as expected and I've been stumped on this one for sometime...
code:
$teamConfig = #(
[pscustomobject]#{
TeamName='Team1';
}
)
$query = "select * from INCAutomation"
$results = Invoke-Sqlcmd -query $query -ServerInstance 'localhost' -Database 'AyushTest'
$collectionWithItems = #()
$array = #()
foreach ($i in $results) {
foreach ($x in $teamConfig) {
if ($i.TeamName -eq $x.TeamName) {
$array += $i.TicketID
$temp = New-Object System.Object
$temp | Add-Member -MemberType NoteProperty -Name "TeamName" -Value $i.TeamName
$temp | Add-Member -MemberType NoteProperty -Name "TeamEmail" -Value $i.TeamEmail
$temp | Add-Member -MemberType NoteProperty -Name "TicketID" -Value $array
$collectionWithItems += $temp
}
}
}
$collectionWithItems = $collectionWithItems | select 'TeamName', 'TeamEmail', 'TicketID' -Unique
$collectionWithItems
Output:
TeamName TeamEmail TicketID
-------- --------- --------
Team1 Team1#test.com {INC0001}
Team1 Team1#test.com {INC0001, INC0002}
Team1 Team1#test.com {INC0001, INC0002, INC0003}
Team1 Team1#test.com {INC0001, INC0002, INC0003, INC0004}
Desired output:
TeamName TeamEmail TicketID
-------- --------- --------
Team1 Team1#test.com {INC0001, INC0002, INC0003, INC0004}
The other thing I am confused about as well is that this will need to work with multiple teams for example:
$teamConfig = #(
[pscustomobject]#{
TeamName='Team1';
}
[pscustomobject]#{
TeamName='Team2';
}
[pscustomobject]#{
TeamName='Team3';
}
)
and as a result, the desired output should be:
TeamName TeamEmail TicketID
-------- --------- --------
Team1 Team1#test.com {INC0001, INC0002, INC0003, INC0004}
Team2 Team2#test.com {INC0005, INC0006, INC0007, INC0008}
Team3 Team3#test.com {INC0009, INC0010, INC0011, INC0012}
Thanks in advance!

Your outer loop should iterate over $teamConfig so that you only go through each team once.
To get the tickets associated with the current team name, you can use Where-Object, like so:
$ticketIds = $results |Where-Object TeamName -eq $team.TeamName |Select -ExpandProperty TicketID
Allowing you to simplify your code greatly:
$teamConfig = #(
[pscustomobject]#{
TeamName = 'Team1'
TeamEmail = 'team1#domain.tld'
}
[pscustomobject]#{
TeamName = 'Team2'
TeamEmail = 'team2#domain.tld'
}
[pscustomobject]#{
TeamName = 'Team3'
TeamEmail = 'team3#domain.tld'
}
)
$query = "select * from INCAutomation"
$results = Invoke-Sqlcmd -query $query -ServerInstance 'localhost' -Database 'AyushTest'
$teamTickets = foreach($team in $teamConfig){
# Filter tickets based on team name
$ticketIds = $results |Where-Object TeamName -eq $team.TeamName |Select -ExpandProperty TicketID
# Output a single object per team, with all ticket IDs attached
$team |Select TeamName,TeamEmail,#{Name='TicketID';Expression={ $ticketIds }}
}
$teamTickets now contains exactly one object per team in $teamConfig.

Related

Add NoteProperty and populate object for eventual CSV export

Full question below, here is the setup:
# Gets all room mailboxes
$rooms = Get-Mailbox -RecipientTypeDetails RoomMailbox -ResultSize Unlimited
if ( !$places ){
$places = #()
# Gets Places for Room Mailboxes
foreach ( $room in $rooms ){
$places += Get-Place $room.PrimarySmtpAddress
}
}
$populatedTags = $places | Where-Object { $_.Tags -notlike $null }
$populatedTags | Select-Object Identity, Tags
Identity Tags
-------- ----
Room1#example.com {Microsoft Teams Room}
Room2#example.com {BYOD}
Room3#example.com {Microsoft Surface Hub}
Room4#example.com {Microsoft Teams Room, Microsoft Surface Hub, BYOD}
$populatedTags[3].Tags.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True ArrayList System.Object
So this is the code I use to obtain data. What I am trying to achieve is a CSV that would look like this:
Identity,TAGS_Microsoft Teams Room,TAGS_BYOD,TAGS_Microsoft Surface Hub
Room1#example.com,TRUE,,
Room2#example.com,,TRUE,
Room3#example.com,,,TRUE
Room4#example.com,TRUE,TRUE,TRUE
I can add the Tags as NoteProperty's by using the below code:
$uniqueTags = $places | Select-Object Tags -Unique
$tags = $uniqueTags.Tags -join ","
$tagsArray = $tags.Split( "," )
$definitiveTags = $tagsArray | Select-Object -Unique
foreach ( $definitiveTag in $definitiveTags ){
if ( $definitiveTag -notlike $null ){
$places | Add-Member -MemberType NoteProperty -Name "TAGS_$definitiveTag" -Value $Null
}
}
Now I can do:
$places[0] | Select-Object Identity, TAGS_*
Identity : Room1#example.com
Microsoft Teams Room :
BYOD :
Microsoft Surface Hub :
Anyone have any ideas on how I populate those fields? I've been scratching my head for far too long.
One simple way is to calculate the value of all the tag properties, and add them to each place object at the same time:
# Gets all room mailboxes
$rooms = Get-Mailbox -RecipientTypeDetails RoomMailbox -ResultSize Unlimited
# Gets Places for Room Mailboxes
$places = foreach ( $room in $rooms ){ Get-Place $room.PrimarySmtpAddress }
# get the list of all used tags
$populatedTags = $places | Where-Object { $_.Tags -notlike $null }
$uniqueTags = $populatedTags | Select-Object Tags -Unique | Where Tags -NotLike $null
# iterate over places
$result = foreach ($place in $places) {
$entry = $place | select Identity #, other, properties, etc
# populate true/null values for each unique tag
foreach ($tagname in $uniqueTags) {
# calculate property value
$value = if ($place.tags -contains $tagname) {'TRUE'} else {$null}
# add property
$entry | Add-Member -MemberType NoteProperty -Name "TAGS_$tagname" -Value $value
}
# add to $result
Write-Output $entry
}
# view a table of places with any tag
$result |
# you cannot filter on a wildcard property, so have to awkwardly check like this
Where {($_.psobject.Properties|where Name -like 'TAGS_*').Value -like 'TRUE'} |
Select Identity,TAGS_*
This is good enough for reports without much data, where the calculations are very simple.

Pscustomobject adding dynamic values

I am trying to read group membership of computers from a particular OU and write to a CSV file. The input criteria for the group membership is like if the computer is part of say "admin" and i need the csv file in the below format
---------------------------------------------------------
Computer Group1 Group2 Group3 Group4
ABCD admin1 admin2 admin3 admin4
EFGH admin2 admin3
XYZZ admin1 admin4
--------------------------------------------------------------
but end up like this.
---------------------------------------------------------
Computer Group1 Group2 Group3 Group4
ABCD admin1 admin2 admin3 admin4
EFGH admin2 admin3
XYZZ admin1 admin4
--------------------------------------------------------------
The code is like this
$All_computer = Get-ADComputer -Filter * -Property * -SearchBase $ou -Server $server | Select-object Name,DNSHostName,Description,memberof
$computerExport = $All_computer |
ForEach-Object {
$ComputerName = $_.Name
$Description = $_.description
$DNSHostname = $_.DNSHostName
$memberof = $_.memberof
$groups = $memberof.where{$_ -like "*$unput_group*"}
$Group_member = [pscustomobject]#{
Workstation = $ComputerName
Comp_Description = $Description
DNS_Hostname = $DNSHostname
}
$i = 0
foreach($group in $Groups)
{
$i++
$member = $group.split('=')[1].split(',')[0]
$Group_member | add-member -MemberType NoteProperty -Name "Group$i" -Value $member
}
$Group_member
}
}
$computerExport | export-csv .\Membership_status.csv -NoTypeInformation
What do i need to do to get the group membership to populate to proper column.
Well of course it does. I mean it's doing what you're asking it to do.
You're only adding the number of properties to the custom object that is found from the where object query. I am really struggling to understand what you're trying to do this for but I THINK what you really want is for each object to have all the possible properties but to have null values for those that don't match that particular computer or better yet to use a boolean.
So... Maybe like this:
[string]$GroupSearch = "admin"
$All_computer = Get-ADComputer -Filter * -Property DNSHostName, Description, memberof -SearchBase $ou -Server $server | Select-Object Name, DNSHostName, Description, memberof
$MatchedGroups = $All_Computer.MemberOf | Sort -Unique | ?{$_.Name -match $GroupSearch}
$computerExport = ForEach ($C in $All_computer) {
$Group_member = [pscustomobject]#{
Workstation = $($C.Name)
Comp_Description = $($C.Description)
DNS_Hostname = $($C.DNSHostName)
}
ForEach ($group in $MatchedGroups) {
[string]$GrpName = $($group.split('=')[1].split(',')[0])
If ($C.MemberOf -contains $group) {
$Group_member | Add-Member -MemberType NoteProperty -Name $GrpName -Value $true
} else {
$Group_member | Add-Member -MemberType NoteProperty -Name $GrpName -Value $false
}
}
$Group_member
}
$computerExport | Export-Csv .\Membership_status.csv -NoTypeInformation
If I understand the question, you need to get all computers from a certain OU that are member of group(s) with a similar partial name.
To do that, I would suggest creating an array of computer objects at first with a temporary extra property called 'Groups' in which the group names that match the partial name are stored.
Later, we'll put these in the correct order as new properties called 'Group1', 'Group2' etc.
# the partial groupname to search for
$unput_group = 'admin'
# Get-ADComputer by default already returns these properties:
# DistinguishedName, DNSHostName, Enabled, Name, ObjectClass, ObjectGUID, SamAccountName, SID, UserPrincipalName
# get an array of computer objects that are member of 'admin*' group(s) with the desired properties
# one extra temporary property is added which contains an array of 'admin*' group names
$All_computer = Get-ADComputer -Filter * -Property Description, MemberOf -SearchBase $ou -Server $server |
Where-Object { $_.MemberOf -match $unput_group} |
Select-Object #{Name = 'Workstation'; Expression = {$_.Name}},
#{Name = 'Comp_Description'; Expression = {$_.Description}},
#{Name = 'DNS_Hostname'; Expression = {$_.DNSHostName}},
#{Name = 'Groups'; Expression = { #($_.MemberOf |
Where-Object { $_ -match "CN=($unput_group[^,]+)" } |
ForEach-Object { $matches[1] }) }}
# get all the group names from the computers we have collected and sort unique
$All_Groups = $All_computer.Groups | Sort-Object -Unique
# build a lookup hashtable with property names ('Group1', 'Group2' etc)
$hash = [ordered]#{}
for ($i = 1; $i -le $All_Groups.Count; $i++) {
$hash["Group$i"] = $All_Groups[$i - 1]
}
# now loop through the collection and add the group properties in order
$result = foreach ($computer in $All_computer) {
foreach ($propertyName in $hash.Keys) {
$group = if ($computer.Groups -contains $hash[$propertyName]) { $hash[$propertyName] }
$computer | Add-Member -MemberType NoteProperty -Name $propertyName -Value $group
}
# output the updated object and remove the temporary 'Groups' property
$computer | Select-Object * -ExcludeProperty Groups
}
# finally, save the results to disk
$result | Export-Csv -Path .\Membership_status.csv -NoTypeInformation

I am doing 3 consecutive foreach loop and it cost a lot of time? Any better way to solve this puzze?

I am trying to get gather data from Active Directory using sAmAccountName and create an output like this.
The requirements is to get all the group membership of the samAccountName on the list
Output
EmployeeID SamAccountName GroupName
---------- -------------- ---------
1 name1 g1
1 name1 g2
2 name2 g3
2 name2 g4
2 name2 g1
Input text file:
List of SamaccountName
name1
name2
My code below is working but it takes too much time since I am doing 3 consecutive for loop. Is there any better way to make it fast for getting the data? I only need 3 columns which is the ID, samAccountName and the groupName
My script
$filter = "Depends on the group filter"
$resultHolder = #()
$accountList = Get-Content "C:\Desktop"
foreach($account in $accountList){
$user = Get-ADUser $account -Properties EmployeeNumber | select EmployeeNumber, name
foreach ($identity in $user.name) {
$getAccounts = Get-ADPrincipalGroupMembership -Identity $identity | select name | Where-Object { $_.name -like $filter}
foreach ($group in $getAccounts.name) {
$table = new-object psobject
$table | Add-Member -NotePropertyName "ID" -NotePropertyValue $user.EmployeeNumber
$table | Add-Member -NotePropertyName "Name" -NotePropertyValue $user.name
$table | Add-Member -NotePropertyName "Group Name" -NotePropertyValue $group
$resultHolder += $table
}
}
}
I don't have an AD available at the moment to test but - assumed you have samaccountnames in your account list - something like this should be enough actually:
$accountList = Get-Content "C:\Desktop"
$Result =
foreach ($account in $accountList) {
$user = Get-ADUser $account -Properties EmployeeNumber, MemberOf
foreach ($Group in $User.MemberOf) {
[PSCustomObject]#{
Name = $user.name
ID = $user.EmployeeNumber
Group = (Get-ADGroup -Identity $Group).Name
}
}
$Result
To make that even faster you should query all your existing groups in AD once and save it in a lookup table you can use then instead of AD queries t oget the name of the groups.

Unable to export custom objects to CSV in powershell

I have written powershell scirpt to generate IIS information to CSV.
$bindingip = Get-WmiObject -Namespace root\webadministration -Class sslbinding2 | Select-Object -
Property PSComputerName,IPAddress
$sitedetails = Get-Website | Select-Object -Property Name,ID,State
$report = New-Object psobject
$report | Add-Member -MemberType NoteProperty -Name "Hostname" -Value $bindingip.PSComputerName
$report | Add-Member -MemberType NoteProperty -Name "IPAddress" -Value $bindingip.IPAddress
$report | Add-Member -MemberType NoteProperty -Name "Site Name" -Value $sitedetails.name
$report | Add-Member -MemberType NoteProperty -Name "ID" -Value $sitedetails.id
$report | Add-Member -MemberType NoteProperty -Name "State" -Value $sitedetails.state
$report | Export-Csv C:\content.csv -NoTypeInformation
Output of CSV:
Hostname IPAddress Site Name ID State
System.Object[] System.Object[] System.Object[] System.Object[] System.Object[]
Do i need to add anything to the code to get exact output, Can anyone help on this.
As Abraham and Ash pointed out in their comments, the values of the properties on your variables $bindingip and $sitedetails are an array instead of a string. This is why when you export the report you get the object type instead of it's actual value.
You could see this by doing for example this:
$bindingip.PSComputerName.GetType()
Which would return something like this:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
However if you select just the first element on the array PSComputerName
$bindingip.PSComputerName[0].GetType()
You would see:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
A workaround for this, is to either convert the values to a multiline string by the use of Out-String or by joining the elements of the arrays with a delimiter i.e. -join '//'.
$bindingip = Get-WmiObject -Namespace root\webadministration -Class sslbinding2
$sitedetails = Get-Website
# Like this (thanks mklement0):
$report = [pscustomobject]#{
Hostname = "$($bindingip.PSComputerName)"
IPAddress = "$($bindingip.IPAddress)"
'Site Name' = "$($sitedetails.name)"
ID = "$($sitedetails.id)"
State = "$($sitedetails.state)"
}
# Or like this:
$report = [pscustomobject]#{
Hostname = ($bindingip.PSComputerName | Out-String).trim()
IPAddress = ($bindingip.IPAddress | Out-String).trim()
'Site Name' = ($sitedetails.name | Out-String).trim()
ID = ($sitedetails.id | Out-String).trim()
State = ($sitedetails.state | Out-String).trim()
}
# Or like this:
$report = [pscustomobject]#{
Hostname = $bindingip.PSComputerName -join '//'
IPAddress = $bindingip.IPAddress -join '//'
'Site Name' = $sitedetails.name -join '//'
ID = $sitedetails.id -join '//'
State = $sitedetails.state -join '//'
}
$report | Export-Csv C:\content.csv -NoTypeInformation
Edit
This can also work, which is what I think mklement0 suggested. It will create a new object for each element on the values of the properties:
$compName = $bindingip.PSComputerName
$ipAddr = $bindingip.IPAddress
$name = $sitedetails.name
$id = $sitedetails.id
$state = $sitedetails.state
$top = ($compName.Count,$ipAddr.Count,$name.Count,$id.Count,$state.Count | Measure-Object -Maximum).Maximum
$report = for($i = 0;$i -lt $top;$i++)
{
[pscustomobject]#{
Hostname = $compName[$i]
IPAddress = $ipAddr[$i]
'Site Name' = $name[$i]
ID = $id[$i]
State = $state[$i]
}
$report | Export-Csv...
In addition to Santiago's very thorough and excellent answer, it appears to me that we are just combining the objects of one array with another to produce all possible combinations. I might do something like the following to accomplish this.
$bindingip = #(
[PSCustomObject]#{
PSComputerName = 'Public1'
IPAddress = '192.168.0.1'
},
[PSCustomObject]#{
PSComputerName = 'Public1'
IPAddress = '127.0.0.1'
}
)
$siteDetails = #(
[PSCustomObject]#{
Name = 'site 1'
Id = 'site1'
State = 'up'
},
[PSCustomObject]#{
Name = 'site 2'
Id = 'site2'
State = 'down'
}
)
$combined = foreach ($ip in $bindingip) {
foreach ($details in $siteDetails) {
$out = [ordered]#{}
$ip.psobject.properties | ForEach-Object {
$out[$_.Name] = $_.Value
}
$details.psobject.properties | ForEach-Object {
$out[$_.Name] = $_.Value
}
[pscustomobject]$out
}
}
$combined | Format-Table
Output
PSComputerName IPAddress Name Id State
-------------- --------- ---- -- -----
Public1 192.168.0.1 site 1 site1 up
Public1 192.168.0.1 site 2 site2 down
Public1 127.0.0.1 site 1 site1 up
Public1 127.0.0.1 site 2 site2 down
One might wrap this in a function for reusability
function Combine-ObjectsFromTwoArrays {
param (
[array]$array1,
[array]$array2
)
foreach ($obj1 in $array1) {
foreach ($obj2 in $array2) {
$out = [ordered]#{}
$obj1.psobject.properties | ForEach-Object {
$out[$_.Name] = $_.Value
}
$obj2.psobject.properties | ForEach-Object {
$out[$_.Name] = $_.Value
}
[pscustomobject]$out
}
}
}

combine object properties into one object in PowerShell

I am trying to combine multiple object properties into one object.
When I have the following code the objects properties are combined.
$computer = gwmi win32_computersystem | select numberOfProcessors, NumberOfLogicalProcessors, HypervisorPresent
$osInfo = gwmi win32_operatingsystem | select version, caption, serialnumber, osarchitecture
Foreach($p in Get-Member -InputObject $osInfo -MemberType NoteProperty)
{
Add-Member -InputObject $computer -MemberType NoteProperty -Name $p.Name -Value $osInfo.$($p.Name) -Force
}
$computer
However, if I replace the above computer and osInfo variables with
$computer = Get-Process | Select processname, path
$osInfo = Get-Service | Select name, status
then the $computer variables does not have the properties of the $osInfo variable after the for loop is executed. ie: the second object is not combined with the first object.
The original code deals with cmdlets that returns two single objects relating to the same source.
You're trying to use it with cmdlets that return arrays of multiple objects.
The following basically merges the two arrays.
$computer = 'Server01'
$collection = #()
$services = Get-Service | Select name, status
$processes = Get-Process | Select processname, path
foreach ($service in $services) {
$collection += [pscustomobject] #{
ServiceName = $service.name
ServiceStatus = $service.status
ProcessName = ""
ProcessPath = ""
}
}
foreach ($process in $processes) {
$collection += [pscustomobject] #{
ServiceName = ""
ServiceStatus = ""
ProcessName = $process.processname
ProcessPath = $process.path
}
}
$collection
Personally, I'd just use the two lines for $services and $processes and be done.
Your problem comes from the bad usage of Get_Member in the case of a collection.
Get-Member -InputObject ACollection gives the members of the collection.
ACollection | Get-Member gives the members of each element of the collection.
So in you case it will work with :
Foreach($p in ($osInfo | Get-Member -MemberType NoteProperty))
{
}
Edited
$computer is also à collection so what do you expect. I add the code of what I think you expect.
$processes = Get-Process | Select processname, path, Id
Foreach($p in $processes)
{
$services = Get-WmiObject "Win32_Service" -filter "ProcessId=$($p.Id)"
Add-Member -InputObject $p -MemberType NoteProperty -Name "Services" -Value $(($services | % {$_.name}) -join ',') -Force
}
$processes