Skip to content

Instantly share code, notes, and snippets.

@AlexanderHolmeset
Created October 13, 2022 08:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AlexanderHolmeset/6d06ca40b9982613173ed3abf76e8f83 to your computer and use it in GitHub Desktop.
Save AlexanderHolmeset/6d06ca40b9982613173ed3abf76e8f83 to your computer and use it in GitHub Desktop.
### Team Owner Routine
### Version 1.0
### Author: Alexander Holmeset
### Email: alexander.holmeset@gmail.com
### Twitter: twitter.com/alexholmeset
### Blog: alexholmeset.blog
$TenantId = "Populate variable"
$ClientID = "Populate variable"
$ClientSecret = Get-AutomationVariable -Name 'secret'
$SharePointSiteID = "Populate variable"
$SharePointListID = "Populate variable"
$SharePointListURL = "https://graph.microsoft.com/v1.0/sites/$($SharePointSiteID)/lists/$($SharePointListID)"
$EmailUPN = "Populate variable"
$TeamsWebhookURL = "Populate variable"
#Number of days between warnings.
$DaysBewteenReminders = 7
$WarningAdminDays = 14
function Get-MSGraphAppToken{
<# .SYNOPSIS
Get an app based authentication token required for interacting with Microsoft Graph API
.PARAMETER TenantID
A tenant ID should be provided.
.PARAMETER ClientID
Application ID for an Azure AD application. Uses by default the Microsoft Intune PowerShell application ID.
.PARAMETER ClientSecret
Web application client secret.
.EXAMPLE
# Manually specify username and password to acquire an authentication token:
Get-MSGraphAppToken -TenantID $TenantID -ClientID $ClientID -ClientSecert = $ClientSecret
.NOTES
Author: Jan Ketil Skanke
Contact: @JankeSkanke
Created: 2020-15-03
Updated: 2020-15-03
Version history:
1.0.0 - (2020-03-15) Function created
#>[CmdletBinding()]
param (
[parameter(Mandatory = $true, HelpMessage = "Your Azure AD Directory ID should be provided")]
[ValidateNotNullOrEmpty()]
[string]$TenantID,
[parameter(Mandatory = $true, HelpMessage = "Application ID for an Azure AD application")]
[ValidateNotNullOrEmpty()]
[string]$ClientID,
[parameter(Mandatory = $true, HelpMessage = "Azure AD Application Client Secret.")]
[ValidateNotNullOrEmpty()]
[string]$ClientSecret
)
Process {
$ErrorActionPreference = "Stop"
# Construct URI
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
# Construct Body
$body = @{
client_id = $clientId
scope = "https://graph.microsoft.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
try {
$MyTokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
$MyToken =($MyTokenRequest.Content | ConvertFrom-Json).access_token
If(!$MyToken){
Write-Warning "Failed to get Graph API access token!"
Exit 1
}
$MyHeader = @{"Authorization" = "Bearer $MyToken" }
}
catch [System.Exception] {
Write-Warning "Failed to get Access Token, Error message: $($_.Exception.Message)"; break
}
return $MyHeader
}
}
$Count=0
#Generate Graph API access token
$global:Header = Get-MSGraphAppToken -TenantID $TenantId -ClientID $ClientID -ClientSecret $ClientSecret
#URL to SharePoint list with entries of sent reminders.
$currentUriSharePoint = "$($SharePointListURL)/items?expand=fields"
#Get all entries in SharePoint list.
$ListItems = while (-not [string]::IsNullOrEmpty($currentUriSharePoint)) {
# API Call
# Write-Host "`r`nQuerying $currentUri..." -ForegroundColor Yellow
$apiCall = Invoke-WebRequest -Method "GET" -Uri $currentUriSharePoint -ContentType "application/json" -Headers $global:Header -ErrorAction Stop -UseBasicParsing
$nextLink = $null
$currentUriSharePoint = $null
if ($apiCall.Content) {
# Check if any data is left
$nextLink = $apiCall.Content | ConvertFrom-Json | Select-Object '@odata.nextLink'
$currentUriSharePoint = $nextLink.'@odata.nextLink'
$apiCall.Content | ConvertFrom-Json
}
}
$ListItems = ($ListItems.value).fields
#Checks if Team in SharePoint list has 1 or 2 owners. If team has 2 owners, then the entry is deleted from the list.
foreach($item in $ListItems){
#Get list of owners of team.
$TeamOwners = @()
try{$TeamOwners = (Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/beta/groups/$($item.TeamID)/owners" -Headers $global:Header).value}
catch{$deleteUri = '$($SharePointListURL)/items/' + $($item.id)
$deleteApiCall = Invoke-WebRequest -Method "DELETE" -Uri $deleteUri -ContentType "application/json" -Headers $global:Header -ErrorAction Stop -UseBasicParsing}
if($TeamOwners.Count -gt 1){
$deleteUri = "$($SharePointListURL)/items/$($item.id)"
$deleteApiCall = Invoke-WebRequest -Method "DELETE" -Uri $deleteUri -ContentType "application/json" -Headers $global:Header -ErrorAction Stop -UseBasicParsing
}
}
$currentUri = 'https://graph.microsoft.com/beta/groups?$select=id,resourceProvisioningOptions,Displayname,mail'
$Teams = while (-not [string]::IsNullOrEmpty($currentUri)) {
# API Call
# Write-Host "`r`nQuerying $currentUri..." -ForegroundColor Yellow
$apiCall = Invoke-WebRequest -Method "GET" -Uri $currentUri -ContentType "application/json" -Headers $global:Header -ErrorAction Stop -UseBasicParsing
$nextLink = $null
$currentUri = $null
if ($apiCall.Content) {
# Check if any data is left
$nextLink = $apiCall.Content | ConvertFrom-Json | Select-Object '@odata.nextLink'
$currentUri = $nextLink.'@odata.nextLink'
$apiCall.Content | ConvertFrom-Json
}
}
$Teams = $Teams.value
#Filters out all groups that are teams.
$Teams = $Teams | Where-Object { $_.resourceProvisioningOptions -eq 'Team' }
$count2 = 0
foreach($team in $teams){
$count2++
"Team $count2"
$TeamDisplayName = @()
$TeamDisplayName = ($($Team.displayname).replace("'","")).replace('"','')
#Get list of owners of team.
$TeamOwners = @()
$TeamOwners = (Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/beta/groups/$($team.id)/owners" -Headers $global:Header).value
#If team has only 1 owner.
If($TeamOwners.Count -eq 1){
$Title="Action Required – Your team is breaking our guidelines"
$bodyHTML ='<doctype html><html><head><title>' + $Title +' </title></head><body><font face="Calibri" size="3">'
$bodyHTML+="<p>Hi $($TeamOwners.givenname)!</p><p>the team<b><i> $TeamDisplayName</i></b>,"
$bodyHTML+=' that you are the owner of is breaking our policy for two owners or more for a team. Please add another owner to the team.
<ul><li>More info here: <a href=https://alexholmeset.blog#for-eiere-og-superbrukere-av-teams>Info about team ownership</a> <br /> <p>Best regards <br /> <b><i> IT Team</i></b></p></font></body></html>'
$BodyEmail = @"
{
"message": {
"subject": "$Title",
"body": {
"contentType": "HTML",
"content": '$bodyHTML'
},
"toRecipients": [
{
"emailAddress": {
"address": "$($teamowners.userPrincipalName)"
}
}
]
},
"saveToSentItems": "false"
}
"@
#Check if team already has an entry in the SharePoint list.
$TeamListItem = @()
$TeamListItem = $ListItems | Where-Object { $_.TeamID -eq $team.id }
#If entry exists in SharePoint list, go on and send new reminder.
If($TeamListItem.TeamID -eq $Team.ID){
#Send 2. warning.
if ($TeamListItem.Warning1 -and !$TeamListItem.Warning2) {
$Days =@()
$Days = ($TeamListItem.Warning1 | New-TimeSpan).Days
#If first warning has been sent, send second warning if enough time has passed.
If($Days -eq $DaysBewteenReminders){
#Send email to team owner.
Invoke-RestMethod -Method POST -Uri "https://graph.microsoft.com/v1.0/users/$($EmailUPN)/sendMail" -Headers $global:Header -body $BodyEmail -ContentType "application/json;charset=utf-8"
$BodyNewListItem = @()
$BodyNewListItem = @"
{
'Warning2': '$(Get-Date)'
}
"@
Invoke-RestMethod -Method Patch -Uri "https://graph.microsoft.com/v1.0/sites/a5c40278-b214-4a00-8a9c-49cfa85e1ce8/lists/e5d86ef4-22e8-46a9-9bc2-901a322cf38b/items/$($TeamListItem.id)/fields" -Headers $global:Header -body $BodyNewListItem -ContentType "application/json"
$teamowners.userPrincipalName
/}
}
#Send 3. warning.
if ($TeamListItem.Warning2 -and !$TeamListItem.Warning3) {
$Days =@()
$Days = ($TeamListItem.Warning2 | New-TimeSpan).Days
#If second warning has been sent, send third warning if enough time has passed.
If($Days -eq $DaysBewteenReminders){
#Send email to team owner.
Invoke-RestMethod -Method POST -Uri "https://graph.microsoft.com/v1.0/users/$($EmailUPN)/sendMail" -Headers $global:Header -body $BodyEmail -ContentType "application/json;charset=utf-8"
$BodyNewListItem = @()
$BodyNewListItem = @"
{
'Warning3': '$(Get-Date)'
}
"@
Invoke-RestMethod -Method Patch -Uri "$($SharePointListURL)/items/$($TeamListItem.id)/fields" -Headers $global:Header -body $BodyNewListItem -ContentType "application/json"
$teamowners.userPrincipalName
}
}
#Send 4. warning.
if ($TeamListItem.Warning3 -and !$TeamListItem.Warning4) {
$Days =@()
$Days = ($TeamListItem.Warning3 | New-TimeSpan).Days
#If third warning has been sent, send fourth warning if enough time has passed.
If($Days -eq $DaysBewteenReminders){
#Send email to team owner.
Invoke-RestMethod -Method POST -Uri "https://graph.microsoft.com/v1.0/users/$($EmailUPN)/sendMail" -Headers $global:Header -body $BodyEmail -ContentType "application/json;charset=utf-8"
$BodyNewListItem = @()
$BodyNewListItem = @"
{
'Warning4': '$(Get-Date)'
}
"@
Invoke-RestMethod -Method Patch -Uri "$($SharePointListURL)/items/$($TeamListItem.id)/fields" -Headers $global:Header -body $BodyNewListItem -ContentType "application/json"
$teamowners.userPrincipalName
}
}
#Send 5. warning.
if ($TeamListItem.Warning4 -and !$TeamListItem.Warning5) {
$Days =@()
$Days = ($TeamListItem.Warning4 | New-TimeSpan).Days
#Om fjerde varsel er sendt, send femte varsel om det har gått lang nokk tid.
#If fourth warning has been sent, send fifth warning if enough time has passed.
If($Days -eq $DaysBewteenReminders){
#Siste epost varsling til eier av Team.
#Final email warning to team owner.
$Title="Last Warning – Your team is breaking our guidelines"
$bodyHTMLsiste ='<doctype html><html><head><title>' + $Title +' </title></head><body><font face="Calibri" size="3">'
$bodyHTML+="<p>Hi $($TeamOwners.givenname)!</p><p>the team<b><i> $TeamDisplayName</i></b>,"
$bodyHTML+=' that you are the owner of is breaking our policy for two owners or more for a team. Please add another owner to the team, or the team will be deleted in 14 days.
<ul><li>More info here: <a href=https://alexholmeset.blog#for-eiere-og-superbrukere-av-teams>Info about team ownership</a> <br /> <p>Best regards <br /> <b><i> IT Team</i></b></p></font></body></html>'
$BodyEmailsiste = @"
{
"message": {
"subject": "$Title",
"body": {
"contentType": "HTML",
"content": '$bodyHTMLsiste'
},
"toRecipients": [
{
"emailAddress": {
"address": "$($teamowners.userPrincipalName)"
}
}
]
},
"saveToSentItems": "false"
}
"@
Invoke-RestMethod -Method POST -Uri "https://graph.microsoft.com/v1.0/users/$($EmailUPN)/sendMail" -Headers $global:Header -body $BodyEmailSiste -ContentType "application/json;charset=utf-8"
$BodyNewListItem = @()
$BodyNewListItem = @"
{
'Warning5': '$(Get-Date)'
}
"@
Invoke-RestMethod -Method Patch -Uri "$($SharePointListURL)/items/$($TeamListItem.id)/fields" -Headers $global:Header -body $BodyNewListItem -ContentType "application/json"
$teamowners.userPrincipalName
}
}
if ($TeamListItem.Warning5 -and !$TeamListItem.WarningAdmin) {
$Days =@()
$Days = ($TeamListItem.Warning5 | New-TimeSpan).Days
#Om femte varsel er sendt, send IT varsel om det har gått lang nokk tid.
If($Days -ge $WarningAdminDays){
$BodyNewListItem = @()
$BodyNewListItem = @"
{
'WarningAdmin': '$(Get-Date)'
}
"@
Invoke-RestMethod -Method Patch -Uri "$($SharePointListURL)/items/$($TeamListItem.id)/fields" -Headers $global:Header -body $BodyNewListItem -ContentType "application/json"
#Team still has 1 owner after 14 days, send message to IT in Teams.
$body = @"
{
"type":"message",
"attachments":[
{
"contentType":"application/vnd.microsoft.card.adaptive",
"contentUrl":null,
"content":{
"`$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
"type":"AdaptiveCard",
"version":"1.2",
"msteams": {
"width": "Full"
},
"body":[
{
"type": "TextBlock",
"text": "$($team.displayname) / $($team.id) can be deleted today!"
}
]
}
}
]
}
"@
Invoke-RestMethod -Method POST -Body $body -Uri $TeamsWebhookURL -ContentType "application/json;charset=utf-8"
}
}
}
Else{
$Count++
$Count
#If not warning has been sent, send first warning and create entry in SharePoint list.
Invoke-RestMethod -Method POST -Uri "https://graph.microsoft.com/v1.0/users/$($EmailUPN)/sendMail" -Headers $global:Header -body $BodyEmail -ContentType "application/json;charset=utf-8"
$BodyNewListItem = @()
$BodyNewListItem = @"
{
'fields': {
'Title': "$TeamDisplayName",
'TeamID': '$($Team.ID)',
'UPN': '$($teamowners.userPrincipalName)',
'Warning1': '$(Get-Date)'
}
}
"@
$TeamOwners
$BodyNewListItem
Invoke-RestMethod -Method POST -Uri "$($SharePointListURL)/items" -Headers $global:Header -body $BodyNewListItem -ContentType "application/json;charset=utf-8"
$teamowners.userPrincipalName
}
}
#If the team has now owner, send message to IT in Teams.
If($TeamOwners.Count -eq 0){
$TeamListItem = @()
$TeamListItem = $ListItems | Where-Object { $_.TeamID -eq $team.id }
#If entry does not exist in SharePoint list, create a entry and warning to IT in Teams.
If($TeamListItem.TeamID -ne $Team.ID){
$body = @"
{
"type":"message",
"attachments":[
{
"contentType":"application/vnd.microsoft.card.adaptive",
"contentUrl":null,
"content":{
"`$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
"type":"AdaptiveCard",
"version":"1.2",
"msteams": {
"width": "Full"
},
"body":[
{
"type": "TextBlock",
"text": "$($team.displayname) / $($team.id) has no owner, and can be deleted today!"
}
]
}
}
]
}
"@
Invoke-RestMethod -Method POST -Body $body -Uri $TeamsWebhookURL -ContentType "application/json;charset=utf-8"
#Create entry in SharePoint list.
$BodyNewListItem = @()
$BodyNewListItem = @"
{
'fields': {
'Title': "$($team.displayName)",
'TeamID': '$($Team.ID)',
'WarningAdmin': '$(Get-Date)'
}
}
"@
Invoke-RestMethod -Method POST -Uri "$($SharePointListURL)/items" -Headers $global:Header -body $BodyNewListItem -ContentType "application/json;charset=utf-8"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment