combined two arrays of different size with different properties - powershell

My goal is to get all group settings and add the total group member count for each group along with the total owners, managers for each group.
Either array can be larger or they can be equal.
I start with getting group member count.
$GroupMembersCount = gam print groups domain <domain.com> members managers owners countsonly | ConvertFrom-Csv
Then I get the settings for each group
$GroupSettings = gam print groups settings | ConvertFrom-Csv
Both arrays share the email property and nothing else.
The $GroupMembersCount is just a count for all the members and what right they have in each group.
looks like
email, manager, member
address.com, 1, 5
newaddress.com, 0,0
anotheraddress,3,3
etc...
the other array $GroupSettings has like 30 column headers and one header is email addresses and the other headers are a bunch of true or false depending on the setting for the group.
I want to combine both arrays into 1.
I know I need to do something like create a new array but the problem I have is how do I combine them since I do not know which will be larger?
$result = $null
$result = $GroupSettings + $GroupMembersCount | Select-Object -Unique
Above did not help
$Max = $null
$Max = ($GroupSettings.Count, $GroupMembersCount.count | Measure-Object -Maximum).Maximum
$resultsarray = $null
$resultsarray = For ($i = 0, $I -lt $max, $I++) {
do stuff
}
If I do something like above which array am I going to index through to get them all ?
The End result will tell me
Which setting each group has, its total members, its total managers and its total owners.
I need this so I can filter groups that have no owners and managers along with other key settings

Use a hashtable to "index" the first data set based on the email address:
$GroupMemberCount = gam print groups domain <domain.com> members managers owners countsonly | ConvertFrom-Csv
$GroupMemberCountByEmail = #{}
$GroupMemberCount |ForEach-Object {
$GroupMemberCountByEmail[$_.email] = $_
}
Now we can use Select-Object to attach the relevant counts to each group setting object based on the email property:
$result = $GroupSettings |Select *,#{Name='MemberCount';Expression={[int]$GroupMemberCountByEmail[$_.email].member}},#{Name='ManagerCount';Expression={[int]$GroupMemberCountByEmail[$_.email].manager}}

Above explained the answer, here I am going to break down what I did and learned for anyone passing by with a similar problem.
What I did:
$GroupMembersCount = $null
$GroupMembersCount = gam print groups domain <domain.com> members managers owners countsonly | ConvertFrom-Csv
$GroupSettings = $null
$GroupSettings = gam print groups settings | ConvertFrom-Csv
$GroupMemberCountByEmail = #{}
$GroupMembersCount | ForEach-Object {
$GroupMemberCountByEmail[$_.email] = $_
}
$GroupSettings | Select-Object *,
#{
Name = 'MembersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].MembersCount }
},#{
Name = 'ManagersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].ManagersCount }
},#{
Name = 'OwnersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].OwnersCount }
} |
Export-Csv 'c:\temp\groups.csv' -NoTypeInformation -Encoding UTF8
The above works. The first part, what #Mathias had me build, was a hash table.
The beauty is in the $result, here, Mathias uses calculated properties. One key point in the $result line is as follows:
#{
Name = 'ManagersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].ManagersCount }
}
The tail of each calculated property, like .managerscount part has to be the value in the hash created above.
There is plenty here I don't understand but, if you are looking hopefully it will lead you like it did for me.

Related

"Sort-Object email -Unique" and if there is duplicate leave the one with the higher role and remove the rest

Sort-Object email -Unique if there is duplicate leave the one with the higher role and remove the rest.
I think I am going to have to do something like this instead of the sort-object
$results = $results | foreach-object {
If ($results.email -contains $_.email) {
Foreach ($role in $results) {
<Find my place in the array then replace with the highest role using a switch>
}
}
}
$results = $results Sort-Object email -Unique
This seems like I am being too complicated and the results don't work out.
I am here for advice on how to do this. I will expand the middle section of my code if there is not a better way to do this ?
If your goal is to get the highest-roled email where emails can be duplicated, you want to ensure you sort by email, then role, and select out the unique emails:
$results |
Sort-Object -Property role, email |
Select-Object -Property email -Unique
This assumes your object shape is:
[pscustomobject]#{
email = 'string'
role = 'string'
type = 'string'
}
For me the answer worked out like the following. I needed to keep the other properties and always keep any duplicate with the highest role.
I could discard any duplicate less then the higher role.
$map = #{ ManagerORcontentManager = 3; writer = 2; commenter = 1 }
$results | Sort-Object email,{ $map[$_.role] } | Group-Object email | ForEach-Object { $_.Group[0] } | Sort-Object email | Export-Csv C:\Reports\$sharedrivename.csv -NoTypeInformation

matching data across two arrays and combining with additional data in array

The Goal
See if $SP.ip is in $NLIP.IpRanges and if it is, add $NLIP.IpRanges and $NLIP.DisplayName to the $SP array or all into a new array.
The Arrays
Array 1 is $SP, it's a CSV import and has the properties 'name' and 'ip', it looks like this:
name: bob
ip: 1.9.8.2
Array 2 is $NLIP and has the relevant properties 'IpRanges' and 'DisplayName'. It's fetched from: $NLIP = Get-AzureADMSNamedLocationPolicy | where-object {$_.OdataType -eq "#microsoft.graph.ipNamedLocation"}, it looks like this:
DisplayName : Named Location 1
IpRanges : {class IpRange {
CidrAddress: 16.29.28.9/28 #fictitious CIDR
}
, class IpRange {
CidrAddress: 1.9.8.3/28 #fictitious CIDR
}
}
The Code / the problem
I'm using IPInRange.ps1 function from https://github.com/omniomi/PSMailTools to find if the IP is in the range. It works like so:
> IPInRange 1.9.8.2 1.9.8.3/28
True
I also worked out that $NLTP.IpRanges.split() | Where-Object ($_ -like "*/*"} can return all the ranges, but $NLIP | Where-Object {$_.IpRanges.split() -like "*/*"} doesn't. I would naturally use the second to keep the variable in the pipe to return the DisplayName. So I'm struggling on how to pull the individual ranges out in such a way that I can then add the 'IpRange' and 'DisplayName' to an array.
Also, maybe it's because I haven't worked out the above issue, but I'm struggling to think how I would iterate through both arrays and combine them into one. I know I would probably enter into a foreach ($item in $SP) and create a temporary array, but after that it's getting hazy.
The result
What I'm hoping to have in the end is:
name: bob
ip: 1.9.8.2
IpRange: 1.9.8.3/28 #fictitious CIDR
DisplayName: Named Location 1
thanks in advance.
I believe this will work for you if I understood the NLIP construct correctly.
We will loop through all the SP objects and see if we can find any NLIP that match the IP range using the IPinRange function you linked. We will then add the 2 properties you want to the SP object if matched and finally pass thru to the pipeline or you can append | export-csv -path YourPath to the end if you would like to send to a csv file
$SP | ForEach-Object {
$target = $_
$matched = $NLIP | ForEach-Object {
$item = $_
# Using where to single out matching range using IPinRange function
$_.IpRanges.Where({ IPInRange -IPAddress $target.ip -Range $_.CidrAddress }) |
ForEach-Object {
# for matching range output custom object containing the displayname and iprange
[PSCustomObject]#{
DisplayName = $item.DisplayName
IpRange = $_.CidrAddress
}
}
}
# add the 2 properties (DisplayName and IpRange) from the match to the original $SP
# object and then pass thru
$target | Add-Member -NotePropertyName DisplayName -NotePropertyValue $matched.DisplayName
$target | Add-Member -NotePropertyName IpRange -NotePropertyValue $matched.IpRange -PassThru
}
By the way, this is how I envisioned the NLIP objects and what I tested with
$NLIP = #(
[pscustomobject]#{
DisplayName = 'Named location 1'
IpRanges = #(
[pscustomobject]#{
CidrAddress = '16.29.28.9/28'
},
[pscustomobject]#{
CidrAddress = '1.9.8.3/28'
}
)
},
[pscustomobject]#{
DisplayName = 'Named location 2'
IpRanges = #(
[pscustomobject]#{
CidrAddress = '16.29.28.25/28'
},
[pscustomobject]#{
CidrAddress = '1.9.8.25/28'
}
)
}
)
Let's to shed some lights in the hazy darkness by first creating a Minimal, Reproducible Example (mcve):
$SP = ConvertFrom-Csv #'
IP, Name
1.9.8.2, BOB
10.10.10.10, Apple
16.29.28.27, Pear
16.30.29.28, Banana
'#
$NLIP = ConvertFrom-Csv #'
IPRange, SubNet
16.29.28.9/28, NetA
1.9.8.3/28, NetB
'#
To tackle this, you need two loops where the second loop is inside the first loop. For the outer loop you might use the ForEach-Object cmdlet which lets you stream each object and with that actually use less memory (assuming that you import the data from a file and eventually export it to a new file). Within the inner loop you might than cross link each IP address with the IPRange using the function you refer to and in case the condition is true create a new PSCustomObject:
$SP |ForEach-Object { # | Import-Csv .\SP.csv |ForEach-Object { ...
ForEach($SubNet in $NLIP) {
if (IPInRange $_.IP $SubNet.IPRange) {
[PSCustomObject]#{
IP = $_.IP
Name = $_.Name
IPRange = $SubNet.IPRange
SubNet = $SubNet.SubNet
}
}
}
} # | Export-Csv .\Output.csv
Which results in:
IP Name IPRange SubNet
-- ---- ------- ------
1.9.8.2 BOB 1.9.8.3/28 NetB
16.29.28.27 Pear 16.29.28.9/8 NetA
16.30.29.28 Banana 16.29.28.9/8 NetA
But as you are considering 3rd party scripts anyways, you might as well use this Join-Object script/Join-Object Module (see also: In Powershell, what's the best way to join two tables into one?):
$SP |Join $NLIP -Using { IPInRange $Left.IP $Right.IPRange }
Which gives the same results.

Listing group membership combinations

Hoping i can get some help in my thinking here because im struggling to accomplish what im after and im doubting its even the best way to achieve this now!
Essentially i have a powershell script (see below) which successfully lists members of a certain filter i put in. This all works smoothly and outputs a list into a CSV.
The next part however is telling it to return only members who are members of specific group combo's. For example, a user might be a member of Group 1, Group 2 and Group 7 (lets say out of 9 groups with that naming scheme)
so im trying to return results of members who only match the statement that they are in TWO groups (Group 1 and Group 2) and exclude those who may only be in Group 1 but not Group 2....hope that makes sense.
# first im narowing down group search to all groups starting with Group and then a number (this is an example). This will return 9 groups. Group 1 through to Group 9
$Groups = (Get-AdGroup -filter * | Where-object { $_.name -like "Group *" } | select-object name -expandproperty name)
# Just standard Array
$Array = #()
$Data = [ordered]#{}
# so now im wanting to search for members in each of those groups we narrowed down to above
Foreach ($Group in $Groups) {
# This bit defines my search criteria. It works perfectly if i just return all users. But if i only want to display members that are in Group 1 AND Group 2....it does not return any results.
$Members = Get-ADGroupMember -identity $Group | Where-Object { ($Group.name -like "Group 1") -and ($Group.name -like "Group 2") } | Get-ADUser -Properties * | select-object givenName, sn, sAMAccountName, mail
# Eventually that will be displayed in the object below...this bit works fine
foreach ($Member in $Members) {
$Data."update" = "modify"
$Data."region" = $Group
$Data."login" = $Member.mail
$Data."first_name" = $Member.givenName
$Data."last_name" = $Member.sn
$Data."approver_level" = "BlankForNow"
#
$DataPSObject = New-Object PSObject -property $Data
#
$Array += $DataPSObject
}
}
#
$Array | Sort-Object -Property login | Export-Csv "D:\Temp\Groups.csv" -NoTypeInformation
Any ideas where i could be going wrong? Maybe its better to edit the outputted CSV and match statements that way. So remove lines from CSV where users are not a member of both? Not even sure if thats entirely possible with Import-CSV tbh
Thanks in advance!
If you want the common members of the groups "Operations" and "ServiceDeskLevel2"
# Get groups members
$membersGroup1 = Get-ADGroupMember "Operations"
$membersGroup2 = Get-ADGroupMember "ServiceDeskLevel2"
# Compares both groups and put common members in the $res list
$res = Compare-Object $membersGroup1 $membersGroup2 -PassThru -IncludeEqual -ExcludeDifferent
# Output the name of the common members from $res
$res | Format-List -Property name

How to look for Active Directory group name from csv in PowerShell?

If I have a .csv:
ClientCode,GroupCode
1234,ABC
1234,DEF
1235,ABC
1236,ABC
and I want to get a hashtable with ClientCode as key, and values to be all AD groups with ClientCode in it, for example:
ClientCode GroupCode
---------- ---------
1234 ClientGroup_InAD_1234, some_other_client_1234
1235 ClientGroup_InAD_1235, some_other_client_in_AD_1235
1236 ClientGroup_InAD_1236
How do I go about this?
Essentially, I have client groups in Active Directory and each client has a code which is the same as the 'ClientCode' in the csv. For example, I might have a client called 'Bob' which I have assigned a code '1234' to it. Therefore, the Group for Bob in the AD would be 'Bob_1234'. Essentially I want to be able to search for whatever groups have ClientCode in them. So i want to search for the all the AD groups that have '1234'. This would return 'Bob_1234' and whatever group in the AD also has '1234' in its name.
So far I have tried:
$clientTable = #{}
foreach($rec in $csv_data) {
$groups = #(get-adgroup -Filter "name -like '*$($rec.clientcode)_*'")
write-host "Found $($groups.count) group(s) for: $($rec.clientcode)"
$clientTable[$ClientCode] = #($groups)
}
$clientTable
but I'm not getting my desired output
You can use the loop like this. You will need to search with a * at the beginning of the name you are looking to find via the Filter.
foreach($rec in $csv) {
$clientCode = "*_$($rec.ClientCode)"
if (!($clientTable.ContainsKey($clientCode))) {
$names = Get-ADGroup -Filter 'Name -like $clientCode' | select Name
$clientTable[$clientCode] = $names -join ","
}
}
This will also check for any client IDs that have already been checked and ignore those.
If you want a hash table populated with the ClientCode value as the key name, you can do the following:
$clientTable = #{}
foreach($rec in $csv_data){
$groups = #(Get-ADGroup -Filter "Name -like '*_$($rec.ClientCode)'" | Select -Expand Name)
write-host "Found $($groups.count) group(s) for: $($rec.ClientCode)"
$clientTable[$rec.ClientCode] = $groups
}
$clientTable
Keep in mind here that each value in the hash table is an array of group names. If you want a single string with comma-delimited names, you can do $clientTable[$rec.ClientCode] = $groups -join "," instead.
You will need to de-duplicate the ClientCodes in the CSV before retrieving the groups.
Something like below should do it (assuming the ClientCode is always preceded by an underscore like in _1234 as shown in your examples)
$csv = Import-Csv -Path 'ClientGroupCodes.csv'
$clientTable = #{}
$csv | Select-Object -ExpandProperty ClientCode -Unique | ForEach-Object {
# $_ is a single clientcode in each iteration (string)
# get an array of goup names
$groups = #(Get-ADGroup -Filter "Name -like '*_$_'" | Select-Object -ExpandProperty Name)
Write-Host "Found $($groups.Count) group(s) for code : '$_'"
$clientTable[$_] = $groups -join ', '
}
$clientTable

Trying to strip out unwanted text in PowerShell

I'm making a PowerShell script which queries our Office 365 tenants and exports certain information into a .csv file. The two fields I'm struggling with are the users default email address and their assigned subscriptions. I can get the data, but not sure how to manipulate it and make it look more presentable.
Get-MSOLUser -All | select firstname,lastname,displayname,islicensed,{$_.Licenses.AccountSkuId},{$_.proxyaddresses -cmatch '^SMTP\:.*'},userprincipalname | sort FirstName | Export-Csv $directory\$tenantname\Export.csv -NoTypeInformation
1) I've managed to get their primary email address as lower cased smtp addresses will always be aliases, but how do I strip out the "SMTP:" part?
2) Instead of "reseller-account:SKU Part Number" I was hoping to shorten this to the names we usually refer them as! Such as:
"E3" instead of "reseller-account:ENTERPRISEPACK"
"E5" instead of "reseller-account:ENTERPRISEPREMIUM"
"ProjectPro" instead of "reseller-account:PROJECTPROFESSIONAL"
"Visio" instead of "reseller-account:VISIOCLIENT"
Two questions really but very similar! Hope you can help.
To Achieve that you can use Calculated Properties along with a small function to convert the SkuId's to a Friendly name and using -replace to remove the SMTP part , I've Created for you a simple function for the conversion, you can add other products just like i did:
The Microsoft Product Name/SKU's list can be found in this link
function Convert-SkuIdToFriendlyName
{
Param(
[string]$SkuId
)
switch ($SkuId)
{
{$SkuId -match "ENTERPRISEPACK"} {return "OFFICE 365 ENTERPRISE E3"}
{$SkuId -match "ENTERPRISEPREMIUM"} {return "OFFICE 365 ENTERPRISE E5"}
default { 'Unknown' }
}
}
Then use a Calculated properties to replace the 'SMTP' part and convert the SkuId:
Get-MSOLUser -All |
Select firstname,lastname,displayname,islicensed,
#{N="License";E={Convert-SkuIdToFriendlyName $_.Licenses.AccountSkuId}},
#{N="Email";E={$_.proxyaddresses -cmatch '^SMTP\:.*' -replace 'SMTP\:'}},userprincipalname |
Sort FirstName
You can use a hashtable as a lookup table for the wanted translations like so:
# create a hash with the desired translations.
# below are just the examples from your question. You need to fill in the rest..
$Licenses = #{
"ENTERPRISEPACK" = "E3"
"ENTERPRISEPREMIUM" = "E5"
"PROJECTPROFESSIONAL" = "ProjectPro"
"VISIOCLIENT" = "Visio"
}
Get-MSOLUser -All |
Select-Object firstname,lastname,displayname,islicensed,userprincipalname,
#{ Name = 'License'; Expression = { $Licenses[$(($_.Licenses.AccountSkuId) -replace '^.+:', '')] }},
#{ Name = 'PrimaryEmailAddress'; Expression = { ($_.proxyaddresses -cmatch '^SMTP\:.*') -replace "SMTP:", "" }} |
Sort-Object FirstName | Export-Csv $directory\$tenantname\Export.csv -NoTypeInformation
In order to get all licenses a user can have listed, the code could be extended to:
# create a hash with the desired translations for the license plans.
# below are just the examples from your question. You need to fill in the rest..
$Licenses = #{
"ENTERPRISEPACK" = "E3"
"ENTERPRISEPREMIUM" = "E5"
"PROJECTPROFESSIONAL" = "ProjectPro"
"VISIOCLIENT" = "Visio"
}
# this calculated property returns all (translated) licenses per user as comma delimited string
$userLicenses = #{
Name = 'Licenses'
Expression = {
$result = #()
foreach($lic in $_.Licenses) {
$result += $Licenses[$(($lic.AccountSkuId) -replace '^.+:', '')]
}
$result -join ', '
}
}
Get-MSOLUser -All |
Select-Object firstname,lastname,displayname,islicensed,userprincipalname,$userLicenses,
#{ Name = 'PrimaryEmailAddress'; Expression = { ($_.proxyaddresses -cmatch '^SMTP\:.*') -replace "SMTP:", "" }} |
Sort-Object FirstName | Export-Csv $directory\$tenantname\Export.csv -NoTypeInformation