Featured image of post Documenting with PowerShell: Documenting Microsoft Teams

Documenting with PowerShell: Documenting Microsoft Teams

I was thinking of creating an automated teams mapping tool, which runs when a user logs on to a new machine to automatically sync all the Teams sites required and joined. I mostly wanted to do this because the current implementation of the registry/GPO method can take up to 8 hours.

During the creation of my tool I noticed I didn’t have an up to date document for all teams. So I figured I would create one. To do this, I’m using the Secure Application Model as always. The only difference is the method we are using to log onto the Graph API.

Logging onto the Graph API requires a token, normally we generate this token using the PartnerCenter module. This works fine for most tasks, but for using Teams with the Graph API we need a different claim, namely the “Client_Credentials” claim.

So instead of using the partner center module, we will create our own request for a token. I’ll show you how in the script.

I’ve created two versions again, one generic, and one for IT-Glue.

Generic version

The generic version creates 1 html file per team in C:\Temp. Feel free to modify this for your own documentation system. The result will look something like this:

```

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.accesstoken)" }

write-host "Starting documentation process for $($customer.name)." -ForegroundColor Green
$headers = @{ "Authorization" = "Bearer $($CustgraphToken.AccessToken)" }
Write-Host "Grabbing all Teams" -ForegroundColor Green
$AllTeamsURI = "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$top=999"
$Teams = (Invoke-RestMethod -Uri $AllTeamsURI -Headers $Headers -Method Get -ContentType "application/json").value
Write-Host "Grabbing all Team Settings" -ForegroundColor Green
$TeamSettings = foreach ($Team in $Teams) {
    $Settings = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/Teams/$($team.id)" -Headers $Headers -Method Get -ContentType "application/json")
    $Members = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups/$($team.id)/members?`$top=999" -Headers $Headers -Method Get -ContentType "application/json").value
    $Owners = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups/$($team.id)/Owners?`$top=999" -Headers $Headers -Method Get -ContentType "application/json").value

    [PSCustomObject]@{
        'Team Name'          = $settings.displayname
        'Team ID'            = $settings.id
        'Description'        = $Settings.description
        'Teams URL'          = $settings.webUrl
        'Messaging Settings' = $settings.messagingSettings
        'Member Settings'    = $Settings.memberSettings
        'Guest Settings'     = $Settings.guestSettings
        'Fun Settings'       = $settings.funSettings
        'Discovery Settings' = $settings.discoverySettings
        'Is Archived'        = $Settings.isArchived
        'Team Owners'        = $Owners | Select-Object Displayname, UserPrincipalname
        'Team Members'       = $Members | Where-Object { $_.UserType -eq "Member" } | Select-Object Displayname, UserPrincipalname
        'Team Guests'        = $Members | Where-Object { $_.UserType -eq "Guest" } | Select-Object Displayname, UserPrincipalname
    }
    $SettingsHTML = $Settings | ConvertTo-Html -as List - -Fragment -PreContent "<h1>Settings<h2>" | Out-String
    $OwnersHTML = $Owners | Select-Object Displayname, UserPrincipalname | ConvertTo-Html -Fragment -PreContent "<h1>Owners<h2>" | Out-String
    $MembersHTML = $Members | Where-Object { $_.UserType -eq "Member" } | Select-Object Displayname, UserPrincipalname | ConvertTo-Html  -Fragment -PreContent "<h1>Members<h2>" | Out-String
    $GuestsHTML = $Members | Where-Object { $_.UserType -eq "Guest" } | Select-Object Displayname, UserPrincipalname | ConvertTo-Html -Fragment -PreContent "<h1>Guests<h2>" | Out-String

    $head, $SettingsHTML, $OwnersHTML, $MembersHTML, $GuestsHTML -replace "<th>", "<th style=`"background-color:#4CAF50`">" | Out-File "C:\Temp\$($Customer.name) - $($Settings.displayname).html"

}

}

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252

### IT-Glue version

So the IT-Glue version is quite a bit longer; I’ve also improved the previous domain matching logic we used to make the script run much faster. This script creates the Flexible Asset for you when running it the first time. We match the teams based on the Team Name. This script can also run from an Azure Function.

```powershell
################### 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
do {
    $AllITGlueContacts += (Get-ITGlueContacts -page_size 1000 -page_number $i).data.attributes
    $i++
    Write-Host "Retrieved $($AllITGlueContacts.count) Contacts" -ForegroundColor Yellow
}while ($AllITGlueContacts.count % 1000 -eq 0 -and $AllITGlueContacts.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            = "Team Name"
                            kind            = "Text"
                            required        = $true
                            "show-in-list"  = $true
                            "use-for-title" = $true
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order           = 2
                            name            = "Team URL"
                            kind            = "Text"
                            required        = $true
                            "show-in-list"  = $false
                            "use-for-title" = $false
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order          = 3
                            name           = "Team Message settings"
                            kind           = "Textbox"
                            required       = $false
                            "show-in-list" = $false
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order          = 4
                            name           = "Team Member settings"
                            kind           = "Textbox"
                            required       = $false
                            "show-in-list" = $false
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order          = 5
                            name           = "Team Guest settings"
                            kind           = "Textbox"
                            required       = $false
                            "show-in-list" = $false
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order          = 6
                            name           = "Team Fun Settings"
                            kind           = "Textbox"
                            required       = $false
                            "show-in-list" = $false
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order          = 7
                            name           = "Team Owners"
                            kind           = "Textbox"
                            required       = $false
                            "show-in-list" = $false
                        }
                    },
                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order          = 8
                            name           = "Team Members"
                            kind           = "Textbox"
                            required       = $false
                            "show-in-list" = $false
                        }
                    },                    @{
                        type       = "flexible_asset_fields"
                        attributes = @{
                            order          = 9
                            name           = "Team Guests"
                            kind           = "Textbox"
                            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
    $AllTeamsURI = "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$top=999"
    $Teams = (Invoke-RestMethod -Uri $AllTeamsURI -Headers $Headers -Method Get -ContentType "application/json").value
    Write-Host "Grabbing all Team Settings" -ForegroundColor Green
    foreach ($Team in $Teams) {
        $Settings = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/Teams/$($team.id)" -Headers $Headers -Method Get -ContentType "application/json")
        $Members = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups/$($team.id)/members?`$top=999" -Headers $Headers -Method Get -ContentType "application/json").value
        $Owners = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/groups/$($team.id)/Owners?`$top=999" -Headers $Headers -Method Get -ContentType "application/json").value

        $FlexAssetBody =
        @{
            type       = 'flexible-assets'
            attributes = @{
                traits = @{
                    'team-name'             = $settings.displayname
                    'team-url'              = $settings.webUrl
                    'team-message-settings' = ($settings.messagingSettings | convertto-html -Fragment -as list | out-string) -replace $TableStyling
                    'team-member-settings'  = ($Settings.memberSettings | convertto-html -Fragment -as list | out-string) -replace $TableStyling
                    "team-guest-settings"   = ($Settings.guestSettings | convertto-html -Fragment -as list | out-string) -replace $TableStyling
                    "team-fun-settings"     = ($settings.funSettings | convertto-html -Fragment -as list | out-string) -replace $TableStyling
                    'team-owners'           = ($Owners | Select-Object Displayname, UserPrincipalname | convertto-html -Fragment | out-string) -replace $TableStyling
                    'team-members'          = ($Members | Where-Object { $_.UserType -eq "Member" } | Select-Object Displayname, UserPrincipalname | convertto-html -Fragment | out-string) -replace $TableStyling
                    'team-guests'           = ($Members | Where-Object { $_.UserType -eq "Guest" } | Select-Object Displayname, UserPrincipalname | convertto-html -Fragment | out-string) -replace $TableStyling
                }
            }
        }

        write-host "   Uploading $($Settings.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.'team-name' -eq $Settings.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
            }

        }
    }
}

And that’s it! As always, Happy PowerShelling!

All blogs are posted under AGPL3.0 unless stated otherwise
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy