This one was request by a pal of mine that I know via a MSP discord; He wanted a way to document the users in O365 groups and see what type of group it was in one go. To do this, we’re leveraging the Graph API. As in most of my blogs I have a IT-Glue version and a version in HTML for these that use different documentation systems.
For the script you’ll also need the Secure App Model ready, if you haven’t set that up yet, check out the blog and come back to this one 🙂
ITGlue version
The IT-Glue version creates a new Flexible Asset for you, and uploads each groups into their own Flexible Asset. It creates a table of the members and owners, but also tags them. That way you can easily browse to a contact and see what groups they are in.
################### Secure Application Model Information ################### $ApplicationId = 'YourApplicationID' $ApplicationSecret = 'YourApplicationSecret' $RefreshToken = 'YourVeryLongRefreshToken' ################# /Secure Application Model Information #################### ################# IT-Glue Information ###################################### $ITGkey = "YourITGAPIKEY" $ITGbaseURI = "https://api.eu.itglue.com" $FlexAssetName = "Teams - Autodoc" $Description = "Teams information automatically retrieved." $TableStyling = "<th>", "<th style=`"background-color:#4CAF50`">" ################# /IT-Glue Information ##################################### write-host "Checking if IT-Glue Module is available, and if not install it." -ForegroundColor Green #Settings IT-Glue logon information If (Get-Module -ListAvailable -Name "ITGlueAPI") { Import-module ITGlueAPI } Else { Install-Module ITGlueAPI -Force Import-Module ITGlueAPI } #Setting IT-Glue logon information Add-ITGlueBaseURI -base_uri $ITGbaseURI Add-ITGlueAPIKey $ITGkey write-host "Getting IT-Glue contact list" -ForegroundColor Green $i = 0 $AllITGlueContacts = do { $Contacts = (Get-ITGlueContacts -page_size 1000 -page_number $i).data.attributes $i++ $Contacts Write-Host "Retrieved $($Contacts.count) Contacts" -ForegroundColor Yellow }while ($Contacts.count % 1000 -eq 0 -and $Contacts.count -ne 0) write-host "Generating unique ID List" -ForegroundColor Green $DomainList = foreach ($Contact in $AllITGlueContacts) { $ITGDomain = ($contact.'contact-emails'.value -split "@")[1] [PSCustomObject]@{ Domain = $ITGDomain OrgID = $Contact.'organization-id' Combined = "$($ITGDomain)$($Contact.'organization-id')" } } $DomainList = $DomainList | sort-object -Property Combined -Unique write-host "Checking if Flexible Asset exists in IT-Glue." -foregroundColor green $FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data if (!$FilterID) { write-host "Does not exist, creating new." -foregroundColor green $NewFlexAssetData = @{ type = 'flexible-asset-types' attributes = @{ name = $FlexAssetName icon = 'sitemap' description = $description } relationships = @{ "flexible-asset-fields" = @{ data = @( @{ type = "flexible_asset_fields" attributes = @{ order = 1 name = "Group Name" kind = "Text" required = $true "show-in-list" = $true "use-for-title" = $true } }, @{ type = "flexible_asset_fields" attributes = @{ order = 2 name = "Group Settings" kind = "Textbox" required = $true "show-in-list" = $false "use-for-title" = $false } }, @{ type = "flexible_asset_fields" attributes = @{ order = 3 name = "Group Members" kind = "Textbox" required = $false "show-in-list" = $false } }, @{ type = "flexible_asset_fields" attributes = @{ order = 4 name = "Group Owners" kind = "Textbox" required = $false "show-in-list" = $false } }, @{ type = "flexible_asset_fields" attributes = @{ order = 5 name = "Tagged Members" kind = "Tag" "tag-type" = "Contacts" required = $false "show-in-list" = $false } }, @{ type = "flexible_asset_fields" attributes = @{ order = 6 name = "Tagged Owners" kind = "Tag" "tag-type" = "Contacts" required = $false "show-in-list" = $false } } ) } } } New-ITGlueFlexibleAssetTypes -Data $NewFlexAssetData $FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data } write-host "Creating credentials and tokens." -ForegroundColor Green $credential = New-Object System.Management.Automation.PSCredential($ApplicationId, ($ApplicationSecret | Convertto-SecureString -AsPlainText -Force)) $aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal $graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal write-host "Creating body to request Graph access for each client." -ForegroundColor Green $body = @{ 'resource' = 'https://graph.microsoft.com' 'client_id' = $ApplicationId 'client_secret' = $ApplicationSecret 'grant_type' = "client_credentials" 'scope' = "openid" } write-host "Connecting to Office365 to get all tenants." -ForegroundColor Green Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken $customers = Get-MsolPartnerContract -All foreach ($Customer in $Customers) { write-host "Grabbing domains for client $($Customer.name)." -ForegroundColor Green $CustomerDomains = Get-MsolDomain -TenantId $Customer.TenantId write-host "Finding possible organisation IDs" -ForegroundColor Green $orgid = foreach ($customerDomain in $customerdomains) { ($domainList | Where-Object { $_.domain -eq $customerDomain.name }).'OrgID' | Select-Object -Unique } write-host "Documenting in the following organizations." -ForegroundColor Green $ClientToken = Invoke-RestMethod -Method post -Uri "https://login.microsoftonline.com/$($customer.tenantid)/oauth2/token" -Body $body -ErrorAction Stop $headers = @{ "Authorization" = "Bearer $($ClientToken.access_token)" } write-host "Starting documentation process for $($customer.name)." -ForegroundColor Green Write-Host "Grabbing all Teams" -ForegroundColor Green $groups = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups?&`$top=999" -Headers $Headers -Method Get -ContentType "application/json").value Write-Host "Grabbing all Team Settings" -ForegroundColor Green foreach ($group in $Groups) { $Owners = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups/$($group.id)/owners" -Headers $Headers -Method Get -ContentType "application/json").value $Members = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups/$($group.id)/members" -Headers $Headers -Method Get -ContentType "application/json").value $TaggedMembers = foreach ($Member in $Members) { $email = $member.mail #Tagging devices if ($email) { #Write-Host "Finding all related contacts - Based on email: $email" (Get-ITGlueContacts -page_size "1000" -filter_primary_email $email).data start-sleep -miliseconds 110 } } $TaggedOwners = foreach ($Owner in $Owners) { $email = $owner.mail #Tagging devices if ($email) { # Write-Host "Finding all related contacts - Based on email: $email" (Get-ITGlueContacts -page_size "1000" -filter_primary_email $email).data start-sleep -miliseconds 110 } } $GroupSettings = ($group | Select-Object @{Label = "Created on"; Expression = { $_.createdDateTime } }, @{Label = "Created by Application"; Expression = { if (!$_.createdByAppId) { $false } else { $_.createdByAppId } } }, @{Label = "Description"; Expression = { $_.description } }, @{Label = "Mail Enabled"; Expression = { if ($_.Mailenabled) { "Yes" } else { "No " } } }, @{Label = "Security Enabled"; Expression = { if ($_.SecurityEnabled) { "Yes" } else { "No " } } }, @{Label = "Mail Nickname"; Expression = { $_.MailNickname } }, @{Label = "E-Mail addresses"; Expression = { $_.Proxyaddress -join "," } } | convertto-html -Fragment | out-string) -replace $TableStyling $FlexAssetBody = @{ type = 'flexible-assets' attributes = @{ traits = @{ 'group-name' = $group.displayName 'group-settings' = $GroupSettings 'group-members' = ($members | Select-Object Displayname, Userprincipalname | convertto-html -Fragment | out-string) -replace $TableStyling "group-owners" = ($owners | Select-Object Displayname, Userprincipalname | convertto-html -Fragment | out-string) -replace $TableStyling "tagged-owners" = $TaggedOwners.id 'tagged-members' = $TaggedMembers.id } } } write-host " Uploading $($group.displayName) into IT-Glue" -foregroundColor green foreach ($org in $orgID | Select-Object -Unique) { $ExistingFlexAsset = (Get-ITGlueFlexibleAssets -filter_flexible_asset_type_id $FilterID.id -filter_organization_id $org).data | Where-Object { $_.attributes.traits.'group-name' -eq $group.displayName } #If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just upload. if (!$ExistingFlexAsset) { if ($FlexAssetBody.attributes.'organization-id') { $FlexAssetBody.attributes.'organization-id' = $org } else { $FlexAssetBody.attributes.add('organization-id', $org) $FlexAssetBody.attributes.add('flexible-asset-type-id', $FilterID.id) } write-output " Creating new Team: $($Settings.displayName) into IT-Glue organisation $org" New-ITGlueFlexibleAssets -data $FlexAssetBody } else { write-output " Updating Team: $($Settings.displayName)into IT-Glue organisation $org" $ExistingFlexAsset = $ExistingFlexAsset | select-object -Last 1 Set-ITGlueFlexibleAssets -id $ExistingFlexAsset.id -data $FlexAssetBody } } } }
Generic HTML version
The HTML version uses PsWriteHTML to make a nice overview for you. It doesn’t have the tagging options but it’s still pretty nifty.
################### Secure Application Model Information ################### $ApplicationId = 'YourApplicationID' $ApplicationSecret = 'YourApplicationSecret' $RefreshToken = 'YourVeryLongRefreshToken' ################# /Secure Application Model Information #################### write-host "Creating credentials and tokens." -ForegroundColor Green $credential = New-Object System.Management.Automation.PSCredential($ApplicationId, ($ApplicationSecret | Convertto-SecureString -AsPlainText -Force)) $aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal $graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal write-host "Creating body to request Graph access for each client." -ForegroundColor Green $body = @{ 'resource' = 'https://graph.microsoft.com' 'client_id' = $ApplicationId 'client_secret' = $ApplicationSecret 'grant_type' = "client_credentials" 'scope' = "openid" } write-host "Connecting to Office365 to get all tenants." -ForegroundColor Green Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken $customers = Get-MsolPartnerContract -All foreach ($Customer in $Customers) { $ClientToken = Invoke-RestMethod -Method post -Uri "https://login.microsoftonline.com/$($customer.tenantid)/oauth2/token" -Body $body -ErrorAction Stop $headers = @{ "Authorization" = "Bearer $($ClientToken.access_token)" } write-host "Starting documentation process for $($customer.name)." -ForegroundColor Green Write-Host "Grabbing all Teams" -ForegroundColor Green $groups = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups?&`$top=999" -Headers $Headers -Method Get -ContentType "application/json").value Write-Host "Grabbing all Team Settings" -ForegroundColor Green foreach ($group in $Groups) { $Owners = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups/$($group.id)/owners" -Headers $Headers -Method Get -ContentType "application/json").value $Members = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups/$($group.id)/members" -Headers $Headers -Method Get -ContentType "application/json").value $GroupSettings = ($group | Select-Object @{Label = "Created on"; Expression = { $_.createdDateTime } }, @{Label = "Created by Application"; Expression = { if (!$_.createdByAppId) { $false } else { $_.createdByAppId } } }, @{Label = "Description"; Expression = { $_.description } }, @{Label = "Mail Enabled"; Expression = { if ($_.Mailenabled) { "Yes" } else { "No " } } }, @{Label = "Security Enabled"; Expression = { if ($_.SecurityEnabled) { "Yes" } else { "No " } } }, @{Label = "Mail Nickname"; Expression = { $_.MailNickname } }, @{Label = "E-Mail addresses"; Expression = { $_.Proxyaddress -join "," } } | convertto-html -Fragment | out-string) -replace $TableStyling New-HTML { New-HTMLTab -Name "365 Groups: $($group.displayName)" { New-HTMLSection -Invisible { New-HTMLSection -HeaderText 'Settings' { New-HTMLTable -DataTable $GroupSettings } } New-HTMLSection -Invisible { New-HTMLSection -HeaderText "Members" { New-HTMLTable -DataTable ($members | Select-Object Displayname, Userprincipalname) } New-HTMLSection -HeaderText "Owners" { New-HTMLTable -DataTable ($owners | Select-Object Displayname, Userprincipalname) } } } } -FilePath "C:\temp\$($customer.DefaultDomainName).html" -Online } }
but that’s not all! I have some other cool news that’s not really worth a blog of its own, but I’d still like to let you know.
Warranty Script updates
So a while back I created this quickly wacked together script to lookup warranties with PowerShell and create nice looking reports. The main reason for this is that there is a vendor that is showing very shady business practices around Warranty lookups and pricing them.
After a lot of requests of adding functionality or manufacturers I’ve decided to improve the script, turn it into a module and publish it to the world for easier usage. It still needs some love; especially on the help-file side and I’m hoping other community members will contribute to make this the best warranty options available. You can find the project on my Github here.
Feel free to contribute! I’d love to have other people working with me to make sure that these warranty lookup tools price themselves out of the market 😉
And that’s all! as always, Happy PowerShelling.