Entra / Microsoft 365 ยท Users & guests
Sketch pad
Scratch script with experimental PowerShell snippets for Graph, Exchange, and SharePoint tasks under development.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
# Review required modules and connection steps before running.# Connect to Microsoft Graph or Exchange Online as needed for this script.
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $TenantId = (Get-MgOrganization).Id,[int] $LookbackDays = 180,[string] $StartDate = (Get-Date $EndDate).AddDays(-$LookbackDays),[string] $EndDate = (Get-Date).AddHours(1))[array]$NewAttendees = @()# Loop through the users we want to add to the eventForEach ($User in $Users) {# Create hash table containing attendee details$NewAttendee = @{EmailAddress = @{Address = $User.MailName = $User.displayName}Type = "optional" # or "required"}# Add the hash table to the array of new attendees$NewAttendees += $NewAttendee}# Get current attendees for the meeting[array]$CurrentAttendees = $Event.Attendees# Combine the two arrays[array]$UpdatedAttendees = $CurrentAttendees + $NewAttendees# Update the meetingUpdate-MgUserEvent -UserId $CalendarUser.Id -EventId $Event.Id -Attendees $UpdatedAttendees# Get list to update metadata for the new item$ListId = (Get-MgSiteList -SiteId $Site.Id -Filter "DisplayName eq 'Documents'").Id[array]$ListItems = Get-MgSiteListItem -SiteId $Site.Id -ListId $ListId$ListItem = $ListItems[-1]$Body = @{}$Body.Add("Title", "Hard Deleted Users Report Created by Azure Automation")$Status = Update-MgSiteListItemField -SiteId $site.Id -ListId $listId -ListItemId $listItem.Id -BodyParameter $BodyIf ($Status) {Write-Output ("Updated document metadata for item {0} with title {1}" -f $ListItem.Id, $Params.Title)}# Report all OneDrive accounts[array]$Users = Get-MgUser -Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" `-ConsistencyLevel eventual -CountVariable UsersFound -All -PageSize 500If (!$Users) {Write-Host "No user accounts found"Break}$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($User in $Users) {Try {$OneDrive = Get-MgUserDefaultDrive -UserId $User.Id -ErrorAction Stop} Catch {Write-Host ("Unable to find OneDrive for {0}" -f $User.UserPrincipalName)Continue}$ReportLine = [PSCustomObject][Ordered]@{UserPrincipalName = $User.UserPrincipalNameOneDriveUrl = $OneDrive.WebUrlCreated = Get-Date $OneDrive.CreatedDateTime -format 'dd-MMM-yyyy HH:mm'Modified = Get-Date $OneDrive.LastModifiedDateTime -format 'dd-MMM-yyyy HH:mm'}$Report.Add($ReportLine)}# --- Add multiple members from a Microsoft 365 Group to another group$SourceGroup = Get-MgGroup -Filter "DisplayName eq 'Bala Group'"$TargetGroup = Get-MgGroup -Filter "DisplayName eq 'Bedson Project'"[array]$MembersSourceGroup = Get-MgGroupMember -GroupId $SourceGroup.Id -All | Select-Object -ExpandProperty Id[array]$MembersTargetGroup = Get-MgGroupMember -GroupId $TargetGroup.Id -All | Select-Object -ExpandProperty Id# Remove source members who are already members of the target group$MembersSourceGroup = $MembersSourceGroup | Where-Object { $MembersTargetGroup -notcontains $_ }$Data = [System.Collections.Generic.List[Object]]::new()$MembersSourceGroup | ForEach-Object {$Data.Add("https://graph.microsoft.com/beta/directoryobjects/{0}" -f $_)}While ($Data.count -ne 0) {$Parameters = @{"members@odata.bind" = $Data[0..19] }Update-MgGroup -GroupId $TargetGroup.Id -BodyParameter $ParametersIf ($Data.count -gt 20) {$Data.RemoveRange(0.20)} Else {$Data.RemoveRange(0,$Data.count)}}$SelectedUsers = Get-MgUser -Filter "userType eq 'Member'"$MsgFrom = 'Customer.Services@office365itpros.com'# Define some variables used to construct the HTML content in the message body# HTML header with styles$HtmlHead="<html><style>BODY{font-family: Arial; font-size: 10pt;}H1{font-size: 22px;}H2{font-size: 18px; padding-top: 10px;}H3{font-size: 16px; padding-top: 8px;}H4{font-size: 8px; padding-top: 4px;}</style>"$HtmlBody = $null$HtmlBody = $HtmlBody + "<body> <h1>Users</h1><p></p>"$HtmlBody = $HtmlBody + ($SelectedUsers| Sort-Object DisplayName | ConvertTo-HTML -Fragment -As Table -PreContent "<h2>Administrative alert: Inactive Teams based on 30-day lookback</h2>")$HtmlBody = $HtmlBody + "<p>These users are member accounts</p>"$HtmlBody = $HtmlBody + "<p><h4>Generated:</strong> $(Get-Date -Format 'dd-MMM-yyyy HH:mm')</h4></p>"$HtmlMsg = $HtmlHead + $HtmlBody + "<p></body>"$MsgSubject = "Member users"$ToRecipients = @{}$ToRecipients.Add("emailAddress", @{"address"="tony.redmond@office365itpros.com"} )[array]$MsgTo = $ToRecipients# Construct the message body$MsgBody = @{}$MsgBody.Add('Content', "$($HtmlMsg)")$MsgBody.Add('ContentType','html')$Message = @{}$Message.Add('subject', $MsgSubject)$Message.Add('toRecipients', $MsgTo)$Message.Add('body', $MsgBody)$Params = @{}$Params.Add('message', $Message)$Params.Add('saveToSentItems', $true)$Params.Add('isDeliveryReceiptRequested', $true)Send-MgUserMail -UserId $MsgFrom -BodyParameter $Params#-----------$UPN = (Get-MgContext).Account$StartTime = (Get-Date).AddDays(1).ToString("yyyy-MM-ddT00:00:00Z")$EndTime = (Get-Date).AddDays(7).ToString("yyyy-MM-ddT00:00:00Z")$ScheduledStartDateTime = @{}$ScheduledStartDateTime.Add("dateTime", $StartTime)$ScheduledStartDateTime.Add("timeZone", "UTC")$ScheduledEndDateTime = @{}$ScheduledEndDateTime.Add("dateTime", $EndTime)$ScheduledEndDateTime.Add("timeZone", "UTC")$AutomaticRepliesSetting = @{}$AutomaticRepliesSetting.Add("status", "alwaysEnabled")$AutomaticRepliesSetting.Add("externalAudience", "all")$AutomaticRepliesSetting.Add("scheduledEndDateTime", $ScheduledEndDateTime)$AutomaticRepliesSetting.Add("scheduledStartDateTime", $ScheduledStartDateTime)$AutomaticRepliesSetting.Add("internalReplyMessage", "I am out of the office until next week")$AutomaticRepliesSetting.Add("externalReplyMessage", "I am out of the office until next week")$AutoReply = @{}$AutoReply.Add("@odata.context", "https://graph.microsoft.com/v1.0/$UPN/mailboxSettings")$AutoReply.Add("automaticRepliesSetting", $AutomaticRepliesSetting)Update-MgUserMailboxSetting -UserId $UPN -BodyParameter $AutoReply$params = @{"@odata.context" = "https://graph.microsoft.com/v1.0/$metadata#Me/mailboxSettings"automaticRepliesSetting = @{status = "Scheduled"scheduledStartDateTime = @{dateTime = "2026-03-20T18:00:00.0000000"timeZone = "UTC"}scheduledEndDateTime = @{dateTime = "2026-03-28T18:00:00.0000000"timeZone = "UTC"}externalReplyMessage = "I am out of the office until next week"internalReplyMessage = "I am out of the office until next week"externalAudience = "all"}}#+------------- Application Management Policy$PasswordCredentials1 = @{}$PasswordCredentials1.Add("restrictForAppsCreatedAfterDateTime", [System.DateTime]::Parse("2025-01-01T00:00:00Z"))$PasswordCredentials1.Add("restrictionType", "passwordAddition")$PasswordCredentials1.Add("maxLifetime", $null)$PasswordCredentials2 = @{}$PasswordCredentials2.Add("restrictionType", "customPasswordAddition")$PasswordCredentials2.Add("maxLifetime", $null)$PasswordCredentials2.Add("restrictForAppsCreatedAfterDateTime", [System.DateTime]::Parse("2025-01-01T00:00:00Z"))[array]$PasswordCredentials = $PasswordCredentials1, $PasswordCredentials2$ApplicationCredentials = @{}$ApplicationCredentials.Add("passwordCredentials", $PasswordCredentials)$ApplicationPolicyParameters = @{}$ApplicationPolicyParameters.Add("isEnabled", $True)$ApplicationPolicyParameters.Add("applicationRestrictions", $ApplicationCredentials)$ApplicationPolicyParameters.Add("ServicePrincipalRestrictions", $ApplicationCredentials)Update-MgPolicyDefaultAppManagementPolicy -BodyParameter $ApplicationPolicyParameters$Policy = Get-MgPolicyDefaultAppManagementPolicy$Policy.applicationRestrictions.PasswordCredentials#RestrictForAppsCreatedAfterDateTime RestrictionType State#----------------------------------- --------------- -----#01/01/2025 00:00:00 passwordAddition enabled#01/01/2025 00:00:00 customPasswordAddition enabled$params = @{displayName = "Credential management policy"description = "Cred policy sample"isEnabled = $truerestrictions = @{passwordCredentials = @(@{restrictionType = "passwordAddition"state = "enabled"maxLifetime = $nullrestrictForAppsCreatedAfterDateTime = [System.DateTime]::Parse("2025-04-01T10:37:00Z")}@{restrictionType = "passwordLifetime"state = "enabled"maxLifetime = "P90D"restrictForAppsCreatedAfterDateTime = [System.DateTime]::Parse("2025-03-01T00:00:00Z")}@{restrictionType = "symmetricKeyAddition"state = "enabled"maxLifetime = $nullrestrictForAppsCreatedAfterDateTime = [System.DateTime]::Parse("2019-10-19T10:37:00Z")}@{restrictionType = "symmetricKeyLifetime"state = "enabled"maxLifetime = "P90D"restrictForAppsCreatedAfterDateTime = [System.DateTime]::Parse("2014-10-19T10:37:00Z")})keyCredentials = @()}}$AppPolicyParameters = @{displayName = "Restrict App Secrets to 180 days"description = "This policy allows apps to have app secrets lasting for up to 180 days"isEnabled = $truerestrictions = @{passwordCredentials = @(@{restrictionType = "passwordLifeTime"state = "enabled"maxLifetime = 'P180D'restrictForAppsCreatedAfterDateTime = [System.DateTime]::Parse("2025-01-01T00:00:00Z")}@{restrictionType = "passwordAddition"state = "disabled"maxLifetime = $nullrestrictForAppsCreatedAfterDateTime = [System.DateTime]::Parse("2025-01-01T00:00:00Z")})}}# Convert a PowerShell timespan to ISO8601 durationFunction Convert-TimeSpanToISO8601 {param ([Parameter(Mandatory=$true)][TimeSpan]$TimeSpan)$duration = "P"if ($TimeSpan.Days -gt 0) {$duration += "$($TimeSpan.Days)D"}if ($TimeSpan.Hours -gt 0 -or $TimeSpan.Minutes -gt 0 -or $TimeSpan.Seconds -gt 0) {$duration += "T"if ($TimeSpan.Hours -gt 0) {$duration += "$($TimeSpan.Hours)H"}if ($TimeSpan.Minutes -gt 0) {$duration += "$($TimeSpan.Minutes)M"}if ($TimeSpan.Seconds -gt 0) {$duration += "$($TimeSpan.Seconds)S"}}return $duration}# Example usage$timespan = New-TimeSpan -Days 1 -Hours 2 -Minutes 30 -Seconds 45$iso8601Duration = Convert-TimeSpanToISO8601 -TimeSpan $timespanWrite-Output $iso8601Duration# ------------------------ AuditLOgQuery Searches$AuditJobName = ("SharePoint Audit job created at {0}" -f (Get-Date -format 'dd-MMM-yyyy HH:mm'))$AuditQueryStart = (Get-Date $StartDate -format s)$AuditQueryEnd = (Get-Date $EndDate -format s)[array]$AuditOperationFilters = "FileModified", "FileDeleted", "FileUploaded"[array]$AuditobjectIdFilters = "https://redmondassociates.sharepoint.com/sites/blogsandprojects/*", "https://redmondassociates.sharepoint.com/sites/Office365Adoption/*"[array]$AuditAdministrativeUnitIdFilters = "112f5e71-b430-4c83-945b-8b665c14ff25" -as [string][array]$AuditUserPrincipalNameFilters = "Ken.Bowers@office365itpros.com", "Lotte.Vetler@office365itpros.com", "tony.redmond@redmondassociates.org"$AuditQueryParameters = @{}$AuditQueryParameters.Add("@odata.type","#microsoft.graph.security.auditLogQuery")$AuditQueryParameters.Add("displayName", $AuditJobName)$AuditQueryParameters.Add("OperationFilters", $AuditOperationFilters)$AuditQueryParameters.Add("filterStartDateTime", $AuditQueryStart)$AuditQueryParameters.Add("filterEndDateTime", $AuditQueryEnd)$AuditQueryParameters.Add("userPrincipalNameFilters", $AuditUserPrincipalNameFilters)$AuditQueryParameters.Add("objectIdFilters", $AuditobjectIdFilters)# $AuditQueryParameters.Add("administrativeUnitIdFilters", $AuditAdministrativeUnitIdFilters)$Uri = "https://graph.microsoft.com/beta/security/auditLog/queries"$AuditJob = Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $AuditQueryParameters# Check the audit query status every 20 seconds until it completes[int]$i = 1[int]$SleepSeconds = 20$SearchFinished = $false; [int]$SecondsElapsed = 20Write-Host "Checking audit query status..."Start-Sleep -Seconds 30# This cmdlet is not working...#$AuditQueryStatus = Get-MgBetaSecurityAuditLogQuery -AuditLogQueryId $AuditJob.Id$Uri = ("https://graph.microsoft.com/beta/security/auditLog/queries/{0}" -f $AuditJob.id)$AuditQueryStatus = Invoke-MgGraphRequest -Uri $Uri -Method GetWhile ($SearchFinished -eq $false) {$i++Write-Host ("Waiting for audit search to complete. Check {0} after {1} seconds. Current state {2}" -f $i, $SecondsElapsed, $AuditQueryStatus.status)If ($AuditQueryStatus.status -eq 'succeeded') {$SearchFinished = $true} Else {Start-Sleep -Seconds $SleepSeconds$SecondsElapsed = $SecondsElapsed + $SleepSeconds# $AuditQueryStatus = Get-MgBetaSecurityAuditLogQuery -AuditLogQueryId $AuditJob.Id$AuditQueryStatus = Invoke-MgGraphRequest -Uri $Uri -Method Get}}# Fetch the audit records returned by the query# This cmdlet isn't working either# [array]$AuditRecords = Get-MgBetaSecurityAuditLogQueryRecord -AuditLogQueryId $AuditJob.Id -All -PageSize 999$AuditRecords = [System.Collections.Generic.List[string]]::new()$Uri = ("https://graph.microsoft.com/beta/security/auditLog/queries/{0}/records?`$top=999" -f $AuditJob.Id)[array]$AuditSearchRecords = Invoke-MgGraphRequest -Uri $Uri -Method GET[array]$AuditRecords = $AuditSearchRecords.value$NextLink = $AuditSearchRecords.'@Odata.NextLink'While ($null -ne $NextLink) {$AuditSearchRecords = $null[array]$AuditSearchRecords = Invoke-MgGraphRequest -Uri $NextLink -Method GET$AuditRecords += $AuditSearchRecords.valueWrite-Host ("{0} audit records fetched so far..." -f $AuditRecords.count)$NextLink = $AuditSearchRecords.'@odata.NextLink'}Write-Host ("Audit query {0} returned {1} records" -f $AuditJobName, $AuditRecords.Count)$AuditRecords = $AuditRecords | Sort-Object CreatedDateTime -Descending$Uri = "https://graph.microsoft.com/beta/security/auditLog/queries"$Data = Invoke-MgGraphRequest -Uri $Uri -Method GETIf ($Data) {Write-Output "Audit Jobs found"$Data.Value | ForEach-Object {Write-Host ("{0} {1}" -f $_.id, $_.displayName)}} Else {Write-Output "No audit jobs found"}# Full filter$AuditJobName = ("Full audit job created at {0}" -f (Get-Date -format 'dd-MMM-yyyy HH:mm'))$AuditQueryStart = (Get-Date $StartDate -format s)$AuditQueryEnd = (Get-Date $EndDate -format s)$AuditQueryParameters = @{}$AuditQueryParameters.Add("@odata.type","#microsoft.graph.security.auditLogQuery")$AuditQueryParameters.Add("displayName", $AuditJobName)$AuditQueryParameters.Add("filterStartDateTime", $AuditQueryStart)$AuditQueryParameters.Add("filterEndDateTime", $AuditQueryEnd)$Uri = "https://graph.microsoft.com/beta/security/auditLog/queries"$AuditJob = Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $AuditQueryParameters#----------- HTML header$ReportTitle = "Audit Log Report"$DateRun = Get-Date -Format "dd-MMM-yyyy HH:mm"$HtmlHeader = @"<html><head><style>body { font-family: Arial; font-size: 10pt; }h1 { background-color: blue; color: white; padding: 10px; }h2 { font-size: 18px; padding-top: 10px; }h3 { font-size: 16px; padding-top: 8px; }h4 { font-size: 8px; padding-top: 4px; }</style></head><body><h1>$ReportTitle</h1><p>Date Run: $DateRun</p>"@# ---[array]$Mailboxes = Get-Mailbox -ResultSize Unlimited | Where-Object { $_.PrimarySmtpAddress.Split('@')[1] -notin $Domains }[array]$Domains = Get-AcceptedDomain$PrimaryDomain = $Domains | Where-Object { $_.Default -eq $true } | Select-Object -ExpandProperty DomainName[array]$Domains = $Domains | Select-Object -ExpandProperty DomainNameWrite-Host "Checking mailboxes..."[array]$Mailboxes = Get-ExoMailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox, RoomMailbox, EquipmentMailbox, discoveryMailbox$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Mailbox in $Mailboxes) {$ExternalAddresses = $Mailbox.EmailAddresses | Where-Object { $_ -like "SMTP:*" -and ($_.Split(':')[1].Split('@')[1] -notin $Domains) }If ($ExternalAddresses) {$ReportLine = [PSCustomObject][Ordered]@{DisplayName = $Mailbox.DisplayNamePrimarySmtpAddress = $Mailbox.PrimarySmtpAddressEmailAddresses = $ExternalAddresses -join ", "Type = "mailbox"Identity = $Mailbox.Alias}$Report.Add($ReportLine)}}Write-Host "Checking Microsoft 365 Groups..."[array]$Groups = Get-UnifiedGroup -ResultSize UnlimitedForEach ($Group in $Groups) {$ExternalAddresses = $Group.EmailAddresses | Where-Object { $_ -like "SMTP:*" -and ($_.Split(':')[1].Split('@')[1] -notin $Domains) }If ($ExternalAddresses) {$ReportLine = [PSCustomObject][Ordered]@{DisplayName = $Group.DisplayNamePrimarySmtpAddress = $Group.PrimarySmtpAddressEmailAddresses = $ExternalAddresses -join ", "Type = "group"Identity = $Group.Alias}$Report.Add($ReportLine)}}Write-Host "Checking Distribution Lists..."[array]$DLs = Get-DistributionGroup -ResultSize UnlimitedForEach ($DL in $DLs) {$ExternalAddresses = $DL.EmailAddresses | Where-Object { $_ -like "SMTP:*" -and ($_.Split(':')[1].Split('@')[1] -notin $Domains) }If ($ExternalAddresses) {$ReportLine = [PSCustomObject][Ordered]@{DisplayName = $DL.DisplayNamePrimarySmtpAddress = $DL.PrimarySmtpAddressEmailAddresses = $ExternalAddresses -join ", "Type = "dl"Identity = $DL.Alias}$Report.Add($ReportLine)}}Write-Host "Checking Dynamic distribution groups..."[array]$DDLs = Get-DynamicDistributionGroup -ResultSize UnlimitedForEach ($DDL in $DDLs) {$ExternalAddresses = $DDL.EmailAddresses | Where-Object { $_ -like "SMTP:*" -and ($_.Split(':')[1].Split('@')[1] -notin $Domains) }If ($ExternalAddresses) {$ReportLine = [PSCustomObject][Ordered]@{DisplayName = $DDL.DisplayNamePrimarySmtpAddress = $DDL.PrimarySmtpAddressEmailAddresses = $ExternalAddresses -join ", "Type = "ddl"Identity = $DDL.Alias}$Report.Add($ReportLine)}}Write-Host ("{0} mailboxes, {1} groups, {2} distribution lists, and {3} dynamic distribution lists checked" -f $Mailboxes.Count, $Groups.Count, $DLs.Count, $DDLs.Count)Write-Host ("Problems found in {0} objects" -f $Report.Count)$Report | Format-Table -AutoSizeForEach ($Object in $Report) {$UpdatePrimary = $false$NewPrimarySmtpAddress = $null# Check if primary SMTP address needs to be updatedIf ($Object.PrimarySmtpAddress.Split('@')[1] -notin $Domains) {Write-Host ("Primary SMTP address must be updated from {0}" -f $Object.PrimarySmtpAddress)$NewPrimarySmtpAddress = ("{0}@{1}" -f $Object.PrimarySmtpAddress.Split('@')[0], $PrimaryDomain)$UpdatePrimary = $true}If ($UpdatePrimary) {Write-Host ("Setting new primary SMTP address {0}" -f $NewPrimarySmtpAddress)Switch ($Object.Type) {"mailbox" {Set-Mailbox -Identity $Object.Identity -EmailAddresses @{Remove=$Object.PrimarySmtpAddress; Add=$NewPrimarySmtpAddress} -ErrorAction SilentlyContinueSet-Mailbox -Identity $Object.Identity -WindowsEmailAddress $NewPrimarySmtpAddress -ErrorAction SilentlyContinue}"group" {Set-UnifiedGroup -Identity $Object.Identity -PrimarySmtpAddress $NewPrimarySmtpAddress -ErrorAction SilentlyContinue}"dl" {Set-DistributionGroup -Identity $Object.Identity -PrimarySmtpAddress $NewPrimarySmtpAddress -ErrorAction SilentlyContinue}"ddl" {Set-DynamicDistributionGroup -Identity $Object.Identity -PrimarySmtpAddress $NewPrimarySmtpAddress -ErrorAction SilentlyContinue}}}[array]$EmailAddresses = $Object.EmailAddresses -split ", "ForEach ($Address in $EmailAddresses) {If ($Address.Split('@')[1] -notin $Domains) {$AddressToRemove = $Address.Split(':')[1]Write-Host ("Removing address {0} from {1}" -f $Address, $Object.DisplayName)Switch ($Object.Type) {"mailbox" {Set-Mailbox -Identity $Object.Identity -EmailAddresses @{Remove=$AddressToRemove} -ErrorAction SilentlyContinue}"group" {Set-UnifiedGroup -Identity $Object.Identity -EmailAddresses @{Remove=$AddressToRemove} -ErrorAction SilentlyContinue}"dl" {Set-DistributionGroup -Identity $Object.Identity -EmailAddresses @{Remove=$AddressToRemove} -ErrorAction SilentlyContinue}"ddl" {Set-DynamicDistributionGroup -Identity $Object.Identity -EmailAddresses @{Remove=$AddressToRemove} -ErrorAction SilentlyContinue}}}}}[array]$ExoTags = Get-RetentionPolicyTag[array]$M365Tags = Get-ComplianceTag$RetentionTagsHash = @{}ForEach ($Tag in $ExoTags) {$RetentionTagsHash.Add([string]$Tag.Guid, $Tag.Name)}ForEach ($Tag in $M365Tags) {$RetentionTagsHash.Add([string]$Tag.Guid, $Tag.Name)}Write-Host "Looking for audit records..."[array]$Records = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-$LookbackDays) -EndDate (Get-Date) -Operations ApplyPriorityCleanup -ResultSize 5000 -FormattedIf ($Records.Count -eq 0) {Write-Host "No audit records found for ApplyPriorityCleanup operations"Break} Else {$Records = $Records | Sort-Object Identity -Unique | Sort-Object { $_.CreationDate -as [datetime]} -DescendingWrite-Host ("Processing {0} audit records..." -f $Records.Count)}$PriorityCleanupReport = [System.Collections.Generic.List[Object]]::new()ForEach ($Rec in $Records) {$LabelApplied = $null; $LabelID = $null; $LabelRemoved = $null; [string]$TimeStamp = $null$AuditData = $Rec.AuditData | ConvertFrom-Json$LabelApplied = $AuditData.OperationProperties | Where-Object {$_.Name -eq 'TagName'} | Select-Object -ExpandProperty Value$LabelId = $AuditData.OperationProperties | Where-Object {$_.Name -eq 'TagId'} | Select-Object -ExpandProperty Value$LabelRemoved = $AuditData.OperationProperties | Where-Object {$_.Name -eq 'TagReplacedByPriorityCleanup'} | Select-Object -ExpandProperty Value$TimeStamp = Get-Date ($AuditData.CreationTime) -format 'dd-MMM-yyyy HH:mm'$ReportLine = [PSCustomObject][Ordered]@{TimeStamp = $TimeStampUser = $AuditData.UserIdAction = $AuditData.OperationMailbox = $AuditData.MailboxOwnerUPNItem = $AuditData.Item.Subject'Label Applied' = $LabelApplied'Label Id' = $LabelId'Label Removed' = $RetentionTagsHash[$LabelRemoved]}$PriorityCleanupReport.Add($ReportLine)}$PriorityCleanupReport | Group-Object Mailbox -NoElement | Sort-Object Count -Descending | Format-Table Name, Count$UserId = (Get-MgUser -UserId (Get-MgContext).Account).Id# Create simple calendar appointment$EventBody = @{}$EventBody.Add("contentType", "HTML")$EventBody.Add("content", "The TEC 2025 comference event starts with registration and breakfast at 8:30AM. The first session will commence at 9:30AM")$EventStart = @{}$EventStart.Add("dateTime", "2025-09-30T09:00:00")$EventStart.Add("timeZone", "Central Standard Time")$EventEnd = @{}$EventEnd.Add("dateTime", "2025-10-01T17:00:00")$EventEnd.Add("timeZone", "Central Standard Time")$EventLocation = @{}$EventLocation.Add("displayName", "Minneapolis")$EventDetails = @{}$EventDetails.Add("subject", "The Experts Conference 2025")$EventDetails.Add("body", $EventBody)$EventDetails.Add("start", $EventStart)$EventDetails.Add("end", $EventEnd)$EventDetails.Add("location", $EventLocation)$EventDetails.Add("allowNewTimeProposals", $true)$EventDetails.Add("transactionId", (New-Guid))# hash table for attendees$EventAttendees = @()# Each attendde defined as email address and name$Participant1 = @{}$Participant1.add("address","lotte.vetler@office365itpros.com")$Participant1.add("name", "Lotte Vetler")$Participant2 = @{}$Participant2.add("address","otto.flick@office365itpros.com")$Participant2.add("name", "Otto.Flick")$Participant3 = @{}$Participant3.add("address","kim.akers@office365itpros.com")$Participant3.add("name", "Kim Akers")$EventAttendee1 = @{}$EventAttendee1.add("emailaddress", $Participant1)$EventAttendee1.Add("type", "required")$EventAttendee2 = @{}$EventAttendee2.add("emailaddress", $Participant2)$EventAttendee2.Add("type", "optional")$EventAttendee3 = @{}$EventAttendee3.add("emailaddress", $Participant3)$EventAttendee3.Add("type", "optional")$EventAttendees = $EventAttendee1, $EventAttendee2, $EventAttendee3$EventDetails.Add("attendees", $EventAttendees)$Uri =("https://graph.microsoft.com/v1.0/users/{0}/calendar/events" -f $userId)$NewEvent = Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $EventDetails$UpdateEventDetails = @{}$UpdateEventDetails.Add("isOnlineMeeting", $true)$UpdateEventDetails.Add("onlineMeetingProvider", "teamsForBusiness")$UpdateEventDetails.Add("isReminderOn", $true)$UpdateEventDetails.Add("reminderMinutesBeforeStart", 30)$Uri = ("https://graph.microsoft.com/v1.0/{0}/events/{1}" -f $UserId, $NewEvent.Id)$UpdatedEvent = Invoke-MgGraphRequest -Uri $Uri -Method PATCH -Body $UpdateEventDetails$NewEvent = Update-MgUserEvent -UserId $userId -EventId $NewEvent.Id -BodyParameter $UpdateEventDetailsUpdate-MgUserEvent -Userid $Userid -Eventid $NewEvent.Id -IsOnlineMeeting:$true -Importance High -OnlineMeetingProvider 'TeamsforBusiness' -ReminderMinutesBeforeStart 30# Update with attendees - rewrite attendee list$Participant1 = @{}$Participant1.Add("address","James.Ryan@office365itpros.com")$Participant1.Add("name", "James Ryan")$Attendee1 = @{}$Attendee1.Add("type","required")$Attendee1.Add("Emailaddress", $Participant1)[array]$Participants = $Attendee1$EventDetails = @{}$EventDetails.Add("attendees", $Participants)# New recurring event[array]$DaysOfWeek = "Tuesday"$RecurringPattern = @{}$RecurringPattern.Add("type", "weekly")$RecurringPattern.Add("interval", 1)$RecurringPattern.Add("daysOfWeek", $DaysOfWeek)$RecurringPattern.Add("firstDayOfWeek", "monday")$RecurringRange = @{}$RecurringRange.Add("startdate", "2025-04-15T09:00:00")$RecurringRange.Add("enddate", "2025-04-15T09:00:00")$RecurringRange.Add("recurrenceRangeType", "endDate")$RecurrenceRange = @{}$RecurrenceRange.Add("pattern", $RecurringPattern)$RecurrenceRange.Add("range", $RecurringRange)$EventDetails = @{}$EventDetails.Add("recurrence", $RecurrenceRange)$EventBody = @{}$EventBody.Add("contentType", "HTML")$EventBody.Add("content", "Weekly update meeting")$EventStart = @{}$EventStart.Add("dateTime", "2025-04-15T09:00:00")$EventStart.Add("timeZone", "UTC")$EventEnd = @{}$EventEnd.Add("dateTime", "2025-04-15T09:30:00")$EventEnd.Add("timeZone", "UTC")$EventLocation = @{}$EventLocation.Add("displayName", "Royal Garden Hotel, London")$EventDetails.Add("subject", "TEC Roadshow")$EventDetails.Add("body", $EventBody)$EventDetails.Add("start", $EventStart)$EventDetails.Add("end", $EventEnd)$EventDetails.Add("location", $EventLocation)$EventDetails.Add("allowNewTimeProposals", $true)$EventDetails.Add("transactionId", (New-Guid))# hash table for attendees$EventAttendees = @()# Each attendde defined as email address and name$Participant1 = @{}$Participant1.add("address","lotte.vetler@office365itpros.com")$Participant1.add("name", "Lotte Vetler")$Participant2 = @{}$Participant2.add("address","otto.flick@office365itpros.com")$Participant2.add("name", "Otto.Flick")$Participant3 = @{}$Participant3.add("address","kim.akers@office365itpros.com")$Participant3.add("name", "Kim Akers")$EventAttendee1 = @{}$EventAttendee1.add("emailaddress", $Participant1)$EventAttendee1.Add("type", "required")$EventAttendee2 = @{}$EventAttendee2.add("emailaddress", $Participant2)$EventAttendee2.Add("type", "optional")$EventAttendee3 = @{}$EventAttendee3.add("emailaddress", $Participant2)$EventAttendee3.Add("type", "optional")$EventAttendees = $EventAttendee1, $EventAttendee2, $EventAttendee3$EventDetails.Add("attendees", $EventAttendees)$Uri =("https://graph.microsoft.com/v1.0/users/{0}/calendar/events" -f $userId)$NewEvent = Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $EventDetails# Doesn't work at present - The property 'attendees' does not exist on type 'microsoft.graph.attende$UpdatedEvent = Update-MgUserEvent -UserId $Userid -Eventid $NewEvent.Id -Attendees $EventDetails[array]$Records = Search-unifiedauditlog -StartDate (Get-Date).AddDays(-$LookbackDays) -EndDate (Get-Date) `-Formatted -ObjectIds "*.agent" -Operations FileUploaded -ResultSize 5000 -SessionCommand ReturnLargesetIf ($Records) {$Records = $records | Sort-Object Identity -UniqueWrite-Host ("{0} audit records found" -f $Records.Count)} Else {Write-Host "No audit records found"Break}$AgentReport = [System.Collections.Generic.List[Object]]::new()ForEach ($Rec in $Records) {$AuditData = $Rec.AuditData | ConvertFrom-Json$ReportLine = [PSCustomObject][Ordered]@{TimeStamp = Get-Date ($AuditData.CreationTime) -format 'dd-MMM-yyyy HH:mm'User = $AuditData.UserIdAction = $AuditData.OperationSiteURL = $AuditData.SiteURLAgent = $AuditData.SourceFileName}$AgentReport.Add($ReportLine)}$AgentReport = $AgentReport | Sort-Object {$_.TimeStamp -as [datetime]} -Descending$AgentReport | Out-GridView -Title "Custom SharePoint Agent Creation"Write-Host ""Write-Host "Custom agents created in these SharePoint Online sites"$AgentReport | Group-Object SiteURL -NoElement | Sort-Object Count -Descending | Format-Table Name, CountWrite-Host ""Write-Host "Custom agents created by these users"$AgentReport | Group-Object User -NoElement | Sort-Object Count -Descending | Format-Table Name, Count# More# Example: Build attractive HTML report for app role assignment audit records# Sample data (replace with your actual records)$Records = @([PSCustomObject]@{CreatedDateTime = '13-Jun-2025 12:56:09'Action = 'App role assignment added to service principal'Application = 'SDKAutomation'User = 'Tony.Redmond@redmondassociates.org'GrantSource = 'Microsoft Graph permission'SourceId = '5e1e9171-754d-478c-812c-f1755a9a4c2d''New Permissions' = 'Read audit logs data from all services'ServicePrincipalId = '553a9e20-35ee-4ed1-b53e-ed32133996ae'AuditRecordId = '0298fe74-2160-4ec5-bf89-f0f50e7898e1'Operation = 'Add app role assignment to service principal'}# Add more records as needed)# Define HTML style with improved row color for visibility$HtmlStyle = @"<style>body { font-family: Segoe UI, Arial, sans-serif; background: #f4f6f8; color: #222; }h1 { background: #0078d4; color: #fff; padding: 16px; border-radius: 6px 6px 0 0; margin-bottom: 0; }table { border-collapse: collapse; width: 100%; background: #fff; border-radius: 0 0 6px 6px; overflow: hidden; }th, td { padding: 10px 12px; text-align: left; }th { background: #e5eaf1; color: #222; }tr { background: #fff; color: #222; }tr:nth-child(even) { background: #f0f4fa; color: #222; }tr:hover { background: #d0e7fa; color: #222; }.caption { font-size: 14px; color: #555; margin-bottom: 12px; }</style>"@# Convert records to HTML table$HtmlTable = $Report | Select-Object `CreatedDateTime, Action, Application, User, GrantSource, SourceId, 'New Permissions', ServicePrincipalId |ConvertTo-Html -Fragment -PreContent "<div class='caption'>App Role Assignment Audit Records</div>"# Compose full HTML$HtmlReport = @"<html><head>$HtmlStyle<title>App Role Assignment Audit Report</title></head><body><h1>App Role Assignment Audit Report</h1><p>Report generated: $(Get-Date -Format 'dd-MMM-yyyy HH:mm')</p>$HtmlTable</body></html>"@# Output to file or display$ReportPath = "$env:TEMP\AppRoleAssignmentAuditReport.html"$HtmlReport | Out-File -FilePath $ReportPath -Encoding utf8Write-Host "HTML report created: $ReportPath"Start-Process $ReportPath[array]$Users = Get-MgUser -All -filter "usertype eq 'Member' and accountEnabled eq true" `-Property "id,displayName"Set-MgRequestContext -MaxRetry 3 -RetryDelay 3[array]$Users = Get-MgUser -All -filter "usertype eq 'Member' and accountEnabled eq true" `-Property "id,displayName"[int]$Pause = 2500[int]$i=0$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($User in $Users) {$i++Write-Host ("Checking user {0} {1}" -f $i, $User.DisplayName)$Uri = ("https://graph.microsoft.com/v1.0/users/{0}?`$select=id,displayName,userPrincipalName,lastSigninActivity" -f $User.Id)Try {$Data = Invoke-MgGraphRequest -Uri $Uri -Method GET -ResponseHeadersVariable $Response -ErrorAction StopIf ($Data) {$LastSignIn = $null$LastSignIn = $Data.signInActivity.lastSignInDateTimeIf ($null -ne $LastSignIn) {$LastSignIn = Get-Date $LastSignIn -Format 'dd-MMM-yyyy HH:mm'} Else {$LastSignIn = "Never"}$ReportLine = [PSCustomObject][Ordered]@{DisplayName = $Data.displayNameUserPrincipalName = $Data.userPrincipalNameLastSignIn = $LastSignIn}$Report.Add($ReportLine)} Else {Write-Host "No data found for user" $User.DisplayName}} Catch {Write-Host "Error getting user" $User.DisplayNameWrite-Host $_.Exception.MessageContinue}If ($i % 5 -eq 0 -and $i -ne $Users.count) {Write-Host "Processed $i users, pausing for $Pause milliseconds..."; Start-Sleep -Milliseconds $Pause}}Write-Host "Checkiung for OAuth2 Permission Grants..."# Get oAuth2PermissionGrant of Principal consent type (to impersonate a specific user)[array]$Grants = Get-MgOauth2PermissionGrant -Filter "consentType eq 'Principal'" -AllWrite-Host "Finding service principals..."# Find service principals and create a hash table for quick lookup[array]$ServicePrincipals = Get-MgServicePrincipal -AllIf ($ServicePrincipals) {$SPHash = @{}ForEach ($SP in $ServicePrincipals) {$SPHash.Add($SP.Id, $SP.DisplayName)}} Else {Write-Host "No service principals found"Break}Write-Host "Looking for licensed users..."[array]$Users = Get-MgUser -Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" -ConsistencyLevel eventual -CountVariable Records -All -PageSize 999 | Sort-Object displayNameIf ($Users) {$UserHash = @{}ForEach ($User in $Users) {$UserHash.Add($User.Id, $User) }} Else {Write-Host "No licensed users found"Break}Write-Host "Generating report for OAuth2 Permission Grants..."# Generate a report$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Grant in $Grants) {$SP = $ServicePrincipals | Where-Object { $_.Id -eq $Grant.ClientId }If ($SP) {$UserDetails = $UserHash[$Grant.PrincipalId]$Resource = $SPHash[$Grant.ResourceId]$ReportLine = [PSCustomObject][ordered]@{Id = $SP.IdDisplayName = $SP.DisplayNameAppId = $SP.AppIdConsentType = $Grant.ConsentTypeUser = $UserDetails.UserPrincipalNameUserDisplayName = $UserDetails.DisplayNameScope = (($Grant.Scope.Trim().Split(" "))) -join ", "Resource = $Resource}$Report.Add($ReportLine)}}$Report | Out-Gridview -Title "Specific principal delegated OAuth2 permission grants"[array]$Book25 = import-csv book2025Buyers.csv$Book25 = $Book25 | Sort-Object -Property Email -Unique[array]$Book26 = import-csv book2026Buyers.csv$Book26 = $Book26 | Sort-Object -Property Email -Unique$Book26Hash = @{}ForEach ($Buyer in $Book26) {$Book26Hash.Add($Buyer.EmailTrim().ToLower(), $Buyer)}$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Buyer in $Book25) {$LookUpValue = $Buyer.Email.Trim().ToLower()If ($null -eq $Book26Hash[$Buyer.Email]) {$ReportLine = [PSCustomObject][Ordered]@{Name = $Buyer.BuyerEmail = $Buyer.EmailCountry = $Buyer.CountryDate = $Buyer.'Purchase Date'Price = $Buyer.PriceTip = $Buyer.'Tip ($)'}$Report.Add($ReportLine)}}$report | Export-Csv -Path "ToBuy.csv" -NoTypeInformation -Encoding UTF8# Convert UNIX epoch time (seconds since 1970-01-01) to PowerShell DateTime$UnixEpochValue = 1752763429$Date = [DateTimeOffset]::FromUnixTimeSeconds($UnixEpochValue).ToLocalTime().DateTimeWrite-Host "UNIX epoch $UnixEpochValue is" $(Get-Date $Date -format 'dd-MMM-yyyy HH:mm')[Array]$Users = Get-MgUser -All -Sort DisplayName[Array]$ProcessedUsers = @()[int]$i=0Do {ForEach ($User in $Users) {$i++Try {#$Uri = "https://graph.microsoft.com/v1.0/users/{0}?`$select=id,displayName,userPrincipalName,signInActivity" -f $User.Id#$Data = Invoke-MgGraphRequest -Method GET -Uri $Uri$Data = Get-MgUser -UserId $User.Id -Property id,displayName,userPrincipalName,signInActivity -ErrorAction Stop$ProcessedUsers += $DataWrite-Host ("Processed user {0} ({1})" -f $i, $User.DisplayName)} Catch {If ($_.Exception.Message -like "*Too many retries performed*") {Write-Host ("Detected: Too many retries performed error when processing user {0}." -f $User.DisplayName) -ForegroundColor Red# Wait 15 second then reattempt to process userStart-Sleep -Seconds 15Write-Host ("Retrying user {0}" -f $User.DisplayName)$Data = Get-MgUser -UserId $User.Id -Property id,displayName,userPrincipalName,signInActivity$ProcessedUsers += $Data} Else {Write-Host ("Other error: {0}" -f $_.Exception.Message)}}}} While ($i -lt 500)# Disable the People, Files, and Calendar Microsoft 365 Companion Apps from starting automatically$RegistryKey = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\SystemAppData\Microsoft.M365Companions_8wekyb3d8bbwe"[array]$AppStartUpIds = @("$RegistryKey\CalendarStartupId","$RegistryKey\FilesStartupId","$RegistryKey\PeopleStartupId")ForEach ($AppStartupId in $AppStartUpIds) {Try {If (Test-Path $AppStartupId) {# Disable startup state for the appWrite-Host ("Disabling startup state for the {0} companion app" -f $AppStartupId.Split("StartupId")[0].Split("\")[11]) -ForegroundColor GreenSet-ItemProperty -Path $AppStartupId -Name "State" -Value 1 -Type DWORD -ErrorAction Stop} Else {Write-Host ("Couldn't find path to disable startup for the {0} companion app" -f $AppStartupId.Split("StartupId")[0].Split("\")[11]) -ForegroundColor Red}} Catch {Write-Error ("Failed to set State for {0} : {1}" -f $AppStartupId, $_)}}Write-Host "Completed suppressing the startup of the Calendar, Files, and People companion apps"#- Search for audit records for Researcher Copilot agent[array]$Records = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-$LookbackDays) -EndDate (Get-Date) `-Formatted -Operations "CopilotInteraction" -ResultSize 5000 -SessionCommand ReturnLargeSetIf ($Records) {$Records = $records | Sort-Object Identity -UniqueWrite-Host ("{0} audit records found" -f $Records.Count)$Records = $Records | Sort-Object { $_.CreationDate -as [datetime]} -Descending} Else {Write-Host "No audit records found - now scanning for Researcher Copilot agent records"Break}$ResearcherReport = [System.Collections.Generic.List[Object]]::new()ForEach ($Rec in $Records) {$AuditData = $Rec.AuditData | ConvertFrom-JsonIf ($AuditData.AgentName -ne "Researcher") {Continue}[array]$CopilotResources = $AuditData.CopilotEventData.AccessedResourcesIf ($CopilotResources) {$Resources = [System.Collections.Generic.List[Object]]::new()ForEach ($Resource in $CopilotResources) {If ($Resource.Action) {If ($Resource.SiteURL) {$ResourceName = $Resource.SiteURL$ResourceId = $null} Else {$ResourceName = $Resource.Name$ResourceId = $Resource.Id.Split("&")[0]}$ResourcesAccessed = [PSCustomObject][Ordered]@{Action = $Resource.ActionName = $ResourceNameId = $ResourceId}$Resources.Add($ResourcesAccessed)}}$ReportLine = [PSCustomObject][Ordered]@{TimeStamp = Get-Date ($AuditData.CreationTime) -format 'dd-MMM-yyyy HH:mm'User = $AuditData.UserIdAction = $AuditData.OperationResources = $Resources.Name -join "; "}$ResearcherReport.Add($ReportLine)} Else {If ($null -ne $AuditData.CopilotEventData.Messages) {$ReportLine = [PSCustomObject][Ordered]@{TimeStamp = Get-Date ($AuditData.CreationTime) -format 'dd-MMM-yyyy HH:mm'User = $AuditData.UserIdAction = $AuditData.OperationResources = If ($AuditData.CopilotEventData.Messages.count -le 3) {$AuditData.CopilotEventData.Messages.Count}Else {$AuditData.CopilotEventData.Messages.count.toString() + " (likely use of Claude LLM)"}}$ResearcherReport.Add($ReportLine)}}}[array]$Invitations = Get-MgAuditLogDirectoryAudit -All -Filter "activityDisplayName eq 'Invite external user to organization'"If ($Invitations) {Write-Host ("{0} invitation audit records found" -f $Invitations.Count)} Else {Write-Host "No invitation audit records found"}ForEach ($Invitation in $Invitations) {$Inviter = $Invitation.InitiatedBy.User.UserPrincipalName$Invitee = $Invitation.TargetResources[0].DisplayName$InviteeUPN = $Invitation.TargetResources[0].UserPrincipalName$InviteeID = $Invitation.TargetResources[0].Id$InvitationDate = Get-Date $Invitation.ActivityDateTime -Format 'dd-MMM-yyyy HH:mm'Write-Host ("{0} invited {1} ({2}) on {3}" -f $Inviter, $Invitee, $InviteeUPN, $InvitationDate)}# --- 1) Connect to Exchange Online (comment out if you are already connected) ---try {# If you use MFA, omit -UserPrincipalName and sign in interactivelyConnect-ExchangeOnline -ShowBanner:$false | Out-Null} catch {Write-Warning "Could not connect to Exchange Online. If already connected, you can ignore this warning."}# --- 6) Optional: summary on screen ---$export |Group-Object EventTypeName |Sort-Object Count -Descending |Format-Table Name, Count``$Uri = "https://graph.microsoft.com/v1.0/security/alerts_v2?`$filter=serviceSource eq 'DataLossPrevention'&`$orderby=createdDateTime desc&`$top=200"[array]$Alerts = Invoke-MgGraphRequest -Method GET -Uri $Uri[array]$DLPAlerts = Get-MgSecurityAlertV2 -Filter "serviceSource eq 'dataLossPrevention'" -PageSize 500 -All -Sort "CreatedDateTime Desc"$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Alert in $DLPAlerts) {If ($Alert.LastUpdateDateTime) { $LastUpdated = Get-Date $Alert.LastUpdateDateTime -Format 'dd-MMM-yyyy HH:mm'} Else {$LastUpdated = "N/A"}$ReportLine = [PSCustomObject][Ordered]@{Id = $Alert.idTitle = $Alert.titleCreatedDateTime = Get-Date $Alert.createdDateTime -Format 'dd-MMM-yyyy HH:mm'Severity = $Alert.severityStatus = $Alert.statusCategory = $Alert.categoryAssignedTo = $Alert.AssignedToLastUpdateDateTime = $LastUpdatedClassification = $Alert.classification}$Report.Add($ReportLine)}# Report SharePoint RCD Audit Events[array]$Operations = "RestrictContentOrgWideSearchDisabled", "RestrictContentOrgWideSearchEnabled", "SharePointRCDUpdateJustification"[array]$Records = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-$LookbackDays) -EndDate (Get-Date) -Formatted `-SessionCommand ReturnLargeSet -ResultSize 5000 -Operations $Operations$Records = $Records | Sort-Object Identity -Unique$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Rec in $Records) {$AuditData = $Rec.AuditData | ConvertFrom-JsonSwitch ($AuditData.Operation) {"RestrictContentOrgWideSearchEnabled" {$Action = ("Enabled RCD for {0}" -f $AuditData.ObjectId)}"SharePointRCDUpdateJustification" {$Action = ("RCD Justification: {0}" -f $AuditData.RCDJustification)}"RestrictContentOrgWideSearchDisabled" {$Action = ("Disabled RCD for {0}" -f $AuditData.ObjectId)}Default {$Action = $AuditData.Operation}}$ReportLine = [PSCustomObject][Ordered]@{TimeStamp = Get-Date ($AuditData.CreationTime) -format 'dd-MMM-yyyy HH:mm'User = $AuditData.UserIdAction = $AuditData.OperationSiteURL = $AuditData.ObjectIdEventInfo = $Action}$Report.Add($ReportLine)}$Report = $Report | Sort-Object {$_.TimeStamp -as [datetime]} -Descending$Report | Out-GridView -Title "Restricted Content Discovery Audit Records"# Create a calendar event in a Teams channel calendar# Get identifiers for the target team (group) and channel$GroupId = Get-MgGroup -Filter "displayName eq 'Microsoft Graph Gurus'" | Select-Object -ExpandProperty Id$ChannelId = Get-MgTeamChannel -TeamId $GroupId -Filter "displayName eq 'General'" | Select-Object -ExpandProperty Id# Define event details$EventDetails = @{}$EventDetails.Add("subject", "Team Channel Meeting")$EventBody = @{}$EventBody.Add("contentType", "HTML")$EventBody.Add("content", "This is a meeting scheduled in the Teams channel calendar.")$EventDetails.Add("body", $EventBody)$EventStart = @{}$EventStart.Add("dateTime", "2026-01-15T10:00")$EventStart.Add("timeZone", "UTC")$EventDetails.Add("start", $EventStart)$EventEnd = @{}$EventEnd.Add("dateTime", "2026-01-15T11:00")$EventEnd.Add("timeZone", "UTC")$EventDetails.Add("end", $EventEnd)$EventLocation = @{}$EventLocation.Add("displayName", "Teams Channel Meeting")$EventDetails.Add("location", $EventLocation)$EventDetails.Add("isOnlineMeeting", $true)$EventDetails.Add("onlineMeetingProvider", "teamsForBusiness")$EventChannel = @{}$EventChannel.Add("teamId", $GroupId)$EventChannel.Add("channelid", $ChannelId)$EventDetails.Add("channelIdentity", $EventChannel)# URI to create event in channel calendar$Uri = ("https://graph.microsoft.com/v1.0/groups/{0}/calendar/events" -f $GroupId)$NewEvent = Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $EventDetails# Client side filter for mailbox items by size# PR_MESSAGE_SIZE$SizePropId = "Integer 0x0e08"# Size threshold$Threshold = 0.5MB# Date range (example: last 30 days)$since = (Get-Date).AddDays(-$LookbackDays).ToString('s') + "Z"# Server-side filter - find messages with attachments received since $since$filter = "hasAttachments eq true and receivedDateTime ge $since"[array]$Messages = Get-MgUserMailFolderMessage -UserId $User.Id -MailFolderId 'Inbox' `-Filter $filter -All -Select id,subject,sender,receivedDateTime `-ExpandProperty "singleValueExtendedProperties(`$filter=id eq '$SizePropId')"$Report = [System.Collections.Generic.List[Object]]::new()# Client-side size check$Messages | ForEach-Object {$Size = [int64]$_.SingleValueExtendedProperties[0].ValueIf ($Size -gt $Threshold) {$ReportLine = [PSCustomObject][Ordered]@{Subject = $_.SubjectFrom = $_.Sender.EmailAddress.AddressReceivedDateTime = $_.ReceivedDateTimeSizeMB = [math]::Round($size / 1MB, 2)Id = $_.Id}$Report.Add($ReportLine)}}$Report | Sort-Object SizeMB -Descending | Out-GridView -Title "Large Emails in Inbox over $($Threshold / 1MB) MB"$ScopeName = "Permanent Employeesโ$Membership = Get-AdaptiveScopeMembers -Identity $ScopeName -ErrorAction SilentlyContinueIf (!($Membership)) {Write-Output ("No members found for {0} scope" -f $ScopeName )} Else {Write-Output ("{0} members found in {1} scope" -f $Membership.count, $ScopeName)}$DateString = "04/22/2022 21:36:39 +00:00"$ParsedDate = [DateTime]::ParseExact($DateString, "MM/dd/yyyy HH:mm:ss zzz", [System.Globalization.CultureInfo]::InvariantCulture)Write-Host $ParsedDate# UserConfiguration FAI Items in ueer mailboxes$User = (Get-MgContext).Account$UserId = (Get-MgUser -UserId $User).Id# Calendar work hours$Uri = ("https://graph.microsoft.com/beta/users/{0}/mailFolders/Calendar/userConfigurations/WorkHours" -f $UserId)[array]$Data = Invoke-MgGraphRequest -URI $Uri -Method Get -OutputType PSObject[string]$Base64Convert = [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($Data.xmldata))# Parse calendar settings from the XML data[xml]$doc = $Base64Convert$ns = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)$ns.AddNamespace('w','WorkingHours.xsd')$root = $doc.SelectSingleNode('//w:WorkHoursVersion1',$ns)$tz = $root.SelectSingleNode('w:TimeZone',$ns)$tzName = $tz.SelectSingleNode('w:Name',$ns).InnerText$tzBias = [int]$tz.SelectSingleNode('w:Bias',$ns).InnerText$std = $tz.SelectSingleNode('w:Standard',$ns)$stdBias = [int]$std.SelectSingleNode('w:Bias',$ns).InnerText$stdChange = $std.SelectSingleNode('w:ChangeDate',$ns)$stdChangeDate = $stdChange.SelectSingleNode('w:Date',$ns).InnerText$stdChangeTime = $stdChange.SelectSingleNode('w:Time',$ns).InnerText$stdChangeDayOfWeek = $stdChange.SelectSingleNode('w:DayOfWeek',$ns).InnerText$dst = $tz.SelectSingleNode('w:DaylightSavings',$ns)$dstBias = [int]$dst.SelectSingleNode('w:Bias',$ns).InnerText$dstChange = $dst.SelectSingleNode('w:ChangeDate',$ns)$dstChangeDate = $dstChange.SelectSingleNode('w:Date',$ns).InnerText$dstChangeTime = $dstChange.SelectSingleNode('w:Time',$ns).InnerText$dstChangeDayOfWeek = $dstChange.SelectSingleNode('w:DayOfWeek',$ns).InnerText$timeslot = $root.SelectSingleNode('w:TimeSlot',$ns)$start = $timeslot.SelectSingleNode('w:Start',$ns).InnerText$end = $timeslot.SelectSingleNode('w:End',$ns).InnerText$workdays = $root.SelectSingleNode('w:WorkDays',$ns).InnerText$CalendarOptions = [PSCustomObject]@{TimeZoneName = $tzNameTimeZoneBiasMinutes = $tzBiasStandardBiasMinutes = $stdBiasStandardChangeDate = $stdChangeDateStandardChangeTime = $stdChangeTimeStandardChangeDayOfWeek = $stdChangeDayOfWeekDaylightBiasMinutes = $dstBiasDaylightChangeDate = $dstChangeDateDaylightChangeTime = $dstChangeTimeDaylightChangeDayOfWeek = $dstChangeDayOfWeekWorkStart = $startWorkEnd = $endWorkDays = $workdays}# Calendar options$Uri = ("https://graph.microsoft.com/beta/users/{0}/mailFolders/Calendar/userConfigurations/Calendar" -f $UserId)[array]$Data = Invoke-MgGraphRequest -URI $Uri -OutputType PsObject | Select-Object -ExpandProperty structuredDataForEach ($Setting in $Data) {[string]$SettingName = $Setting.keyEntry.values[string]$SettingValue = $Setting.valueEntry.valuesWrite-Host ("{0} : {1}" -f $SettingName, $SettingValue)}[array]$USAiports = "PHL", "SFO", "JFK", "BOS", "LAX", "ORD", "ATL", "DFW", "DEN", "SEA", "MCO", "LAS", "EWR", "IAD", "MIA"[array]$Memory = Import-CSV FlightMemory.csv$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Record in $Memory) {# $ConvertedDate = [DateTime]::ParseExact($Record.Date, "MM-dd-yyyy", [System.Globalization.CultureInfo]::InvariantCulture)$AirLineId = $null; $AircraftId = $null[datetime]$Departure = Get-Date $Record.'Gate Departure (Actual)'[datetime]$Arrival = Get-Date $Record.'Gate Arrival (Actual)'$Duration = New-TimeSpan -Start $Record.'Gate Departure (Actual)' -End $Record.'Gate Arrival (Actual)'Switch ($Record.'Cabin Class') {"Economy" {$FlightClass = "1"}"Premium Economy" {$FlightClass = "4"}"Business" {$FlightClass = "2"}"First" {$FlightClass = "4"}"Private" {$FlightClass = "5"}Default {$FlightClass = "1"}}Switch ($Record.'Seat Type') {"Window" {$SeatType = "1"}"Middle" {$SeatType = "2"}"Aisle" {$SeatType = "3"}"Crew" {$SeatType = "0"}Default {$SeatType = "1"}}Switch ($Record.'Flight Reason') {"Business" {$FlightReason = "2"}"Leisure" {$FlightReason = "1"}"Family" {$FlightReason = "3"}"Other" {$FlightReason = "4"}Default {$FlightReason = "1"}}Switch ($Record.Airline.trim()) {"AAL" {$FlightNumber = "AA" + $Record.Flight$FlightAirline = "American Airlines (AA/AAL)"$AirlineID = '4'}"EIN" {$FlightNumber = "EI" + $Record.Flight$FlightAirline = "Aer Lingus (EI/EIN)"$AirlineID = '260'}"ASA" {$FlightNumber = "AS" + $Record.Flight$FlightAirline = "Alaska Airlines (AS/ASA)"$AirlineID = '79'}"AZA" {$FlightNumber = "AZ" + $Record.Flight$FlightAirline = "Alitalia (AZ/AZA)"$AirlineID = '105'}"BAW" {$FlightNumber = "BA" + $Record.Flight$FlightAirline = "British Airways (BA/BAW)"$AirlineID = '113'}"BCY" {$FlightNumber = "WX" + $Record.Flight$FlightAirline = "CityJet (WX/BCY)"$AirlineID = '122'}"BMI" {$FlightNumber = "BD" + $Record.Flight$FlightAirline = "British Midland International (BD/BMI)"$AirlineID = '137'}"AFR" {$FlightNumber = "AF" + $Record.Flight$FlightAirline = "Air France (AF/AFR)"$AirlineID ='34'}"ACA" {$FlightNumber = "AC" + $Record.Flight$FlightAirline = "Air Canada (AC/ACA)"$AirlineID = '13'}"COA" {$FlightNumber = "CO" + $Record.Flight$FlightAirline = "Continental Airlines (CO/COA)"$AirlineID = '196'}"DAL" {$FlightNumber = "DL" + $Record.Flight$FlightAirline = "Delta Airlines (DL/DAL)"$AirlineID = '223'}"DLH" {$FlightNumber = "LH" + $Record.Flight$FlightAirline = "Lufthansa (LH/DLH)"$AirlineID = '239'}"EWG" {$FlightNumber = "EW" + $Record.Flight$FlightAirline = "Eurowings (EW/EWG)"$AirlineID = '282'}"ETD" {$FlightNumber = "EY" + $Record.Flight$FlightAirline = "Etihad Airways (EY/ETD)"$AirlineID = '276'}"FIN" {$FlightNumber = "AY" + $Record.Flight$FlightAirline = "Finnair (AY/FIN)"$AirlineID = '304'}"JBU" {$FlightNumber = "B6" + $Record.Flight$FlightAirline = "JetBlue Airways (B6/JBU)"$AirlineID = '410'}"KLM" {$FlightNumber = "KL" + $Record.Flight$FlightAirline = "KLM Royal Dutch Airlines (KL/KLM)"$AirlineID = '440'}"ICE" {$FlightNumber = "FI" + $Record.Flight$FlightAirline = "Icelandair (FI/ICE)"$AirlineID = '381'}"OSR" {$FlightNumber = "OS" + $Record.Flight$FlightAirline = "Austrian Airlines (OS/OSR)"$AirlineID = '87'}"QFA" {$FlightNumber = "QF" + $Record.Flight$FlightAirline = "Qantas (QF/QFA)"$AirlineID = '631'}"PAI" {$FlightNumber = "PI" + $Record.Flight$FlightAirline = "Piedmont Airlines (PI/PAI)"$AirlineID = '600'}"RYR" {$FlightNumber = "FR" + $Record.Flight$FlightAirline = "Ryanair (FR/RYR)"$AirlineID = '668'}"SAS" {$FlightNumber = "SK" + $Record.Flight$FlightAirline = "Scandinavian Airlines (SK/SAS)"$AirlineID = '677'}"SWR" {$FlightNumber = "LX" + $Record.Flight$FlightAirline = "Swiss International Air Lines (LX/SWR)"$AirlineID = '741'}"UAL" {$FlightNumber = "UA" + $Record.Flight$FlightAirline = "United Airlines (UA/UAL)"$AirlineID = '808'}"USA" {$FlightNumber = "US" + $Record.Flight$FlightAirline = "US Airways (US/USA)"$AirlineID = '1401'}"VAU" {$FlightNumber = "VA" + $Record.Flight$FlightAirline = "Virgin Australia (VA/VAU)"$AirlineID = '1130'}"VIR" {$FlightNumber = "VS" + $Record.Flight$FlightAirline = "Virgin Atlantic Airways (VS/VST)"$AirlineID = '835'}Default {$FlightNumber = $Record.Flight$FlightAirline = $Record.Airline}}Switch ($Record.'Aircraft Type Name'.trim()) {"Airbus 300" {$AircraftType = "Airbus A300 (A300)"$AircraftId = '2034'}"Airbus 310" {$AircraftType = "Airbus A310 (A310)"$AircraftId = '24'}"Airbus 319" {$AircraftType = "Airbus A319 (A319)"$AircraftId = '26'}"Airbus 320" {$AircraftType = "Airbus A320 (A320)"$AircraftId = '27'}"Airbus 321 Neo" {$AircraftType = "Airbus A321neo (A21N)"$AircraftId = '2027'}"Airbus 330-200" {$AircraftType = "Airbus A330-200 (A332)"$AircraftId = '30'}"Airbus 330-300" {$AircraftType = "Airbus A330-300 (A333)"$AircraftId = '31'}"BAE Avro RJ100" {$AircraftType = "Avro RJ100 (ARJ1)"$AircraftId = '2008'}"BAe 146" {$AircraftType = "BAe 146-300 (BEA146)"$AircraftId = '1518'}"B727-200" {$AircraftType = "Boeing 727-200 (B722)"$AircraftId = '2013'}"Boeing 727-200" {$AircraftType = "Boeing 727-200 (B722)"$AircraftId = '2013'}"B727" {$AircraftType = "Boeing 727-200 (B722)"$AircraftId = '2013'}"B737-200" {$AircraftType = "Boeing 737-200 (B732)"$AircraftId = '225'}"B737-300" {$AircraftType = "Boeing 737-300 (B733)"$AircraftId = '226'}"B737-400" {$AircraftType = "Boeing 737-400 (B734)"$AirlineID = '227'}"Boeing 737-400" {$AircraftType = "Boeing 737-400 (B734)"$AirlineID = '227'}"B737-500" {$AircraftType = "Boeing 737-500 (B735)"$AircraftId = '228'}"B737-600" {$AircraftType = "Boeing 737-600 (B736)"$AircraftId = '229'}"Boeing 737-700" {$AircraftType = "Boeing 737-700 (B737)"$AircraftId = '230'}"Boeing 737-800" {$AircraftType = "Boeing 737-800 (B738)"$AircraftId = '231'}"B747-100" {$AircraftType = "Boeing 747-100 (B741)"$AircraftId = '234'}"B747-236" {$AircraftType = "Boeing 747-200 (B742)"$AircraftId = '236'}"B747-400" {$AircraftType = "Boeing 747-400 (B744)"$AircraftId = '237'}"B757" {$AircraftType = "Boeing 757-200 (B752)"$AircraftId = '241'}"B757-200" {$AircraftType = "Boeing 757-200 (B752)"$AircraftId = '241'}"Boeing 767-200" {$AircraftType = "Boeing 767-200 (B762)"$AircraftId = '243'}"Boeing 767-300" {$AircraftType = "Boeing 767-300 (B763)"$AircraftId = '244'}"Boeing 777-200" {$AircraftType = "Boeing 777-200 (B772)"$AircraftId = '246'}"Concorde" {$AircraftType = "Concorde (CONC)"}"Dash 8" {$AircraftType = "De Havilland Canada Dash 8-400 (DH4)"}"Embraer 120" {$AircraftType = "Embraer EMB 120 Brasilia (EMB120)"}"Embraer 145" {$AircraftType = "Embraer ERJ 145 (ERJ145)"$AircraftId = '653'}"Fokker 100" {$AircraftType = "Fokker 100 (F100)"$AircraftId = '710'}"Fokker 50" {$AircraftType = "Fokker 50 (F50)"$AircraftId = '732'}"Fokker F28" {$AircraftType = "Fokker F28 Fellowship (F28)"$AircraftId = '726'}"Jet-410" {$AircraftType = "Bombardier CRJ-100/200 (CRJ2)"$AircraftId = '528'}"Lockheed Tristar" {$AircraftType = "Lockheed L-1011 TriStar (L101)"$AircraftId = '223'}"McDonnell Douglas-800" {$AircraftType = "McDonnell Douglas MD-80 (MD80)"$AircraftId = '1184'}"McDonnell Douglas-81" {$AircraftType = "McDonnell Douglas MD-81 (MD81)"$AircraftId = '1185'}"McDonnell Douglas-82" {$AircraftType = "McDonnell Douglas MD-82 (MD82)"$AircraftId = '1186'}"McDonnell Douglas-83" {$AircraftType = "McDonnell Douglas MD-83 (MD83)"$AircraftId = '1187'}"McDonnell Douglas-87" {$AircraftType = "McDonnell Douglas MD-87 (MD87)"$AircraftId = '1188'}"McDonnell Douglas-88" {$AircraftType = "McDonnell Douglas MD-88 (MD88)"$AircraftId = '1189'}"McDonnell Douglas-90" {$AircraftType = "McDonnell Douglas MD-90 (MD90)"}"McDonnell Douglas-9" {$AircraftType = "McDonnell Douglas MD-90 (MD90)"}"Airbus 321" {$AircraftType = "Airbus A321 (A321)"$AircraftId = '28'}"ATR 42-500" {$AircraftType = "ATR 42-500 (AT42)"$AircraftId = '2033'}Default {$AircraftType = $Record.'Aircraft Type Name'}}$ReportLine = [PSCustomObject][Ordered]@{Date = Get-Date $Record.'Gate departure (actual)' -format yyyy-MM-dd'Flight number' = $FlightNumberFrom = $Record.FromTo = $Record.To'Dep time' = Get-Date $Departure -format 'HH:mm:ss''Arr time' = Get-Date $Arrival -format 'HH:mm:ss'Duration = ("{0}:{1}:{2}0" -f $Duration.Hours, $Duration.Minutes, $Duration.Seconds)Airline = $FlightAirlineAircraft = $AircraftTypeRegistration = $Record.'Tail Number''Seat number' = $Record.Seat'Seat type' = $SeatType'Flight class' = $FlightClass'Flight reason' = $FlightReasonNote = $Record.NotesAirline_id = $AirlineIDAircraft_id = $AircraftId}$Report.Add($ReportLine)}# Code for Azure Automation runbook to enable a conditional access policy to block weekend access for a specific group, and revoke sessions for all members of that group# Requires Policy.ReadWrite.ConditionalAccess, Group.Read.All, User.RevokeSessions.All, and GroupMember.Read.All permissionsConnect-MgGraph -Identity[array]$Users = Get-MgGroupMember -GroupId (Get-MgGroup -Filter "displayName eq 'CA Block Weekend Access (France)'").Id# Revoke access for each memberForEach ($User in $Users) {Try {$Status = Revoke-MgUserSignInSession -UserId $User.Id -ErrorAction StopWrite-Output ("Revoked access for {0}" -f $User.additionalProperties.displayName)} Catch {If ($_.Exception.Message -match "temporarily") {Write-Warning ("Temporarily unable to revoke access for {0}, will retry after 15 seconds" -f $User.additionalProperties.DisplayName)Start-Sleep -Seconds 5Revoke-MgUserSignSession -UserId $User.IdWrite-Output ("Revoked access for {0}" -f $User.additionalProperties.displayName)} Else {Write-Error ("Failed to revoke access for {0}: {1}" -f $User.additionalProperties.displayName, $_.Exception.Message)}}}# Now enable the conditional access policy to block access for this group over the weekend$Policy = Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq 'Block Weekend Access'"If ($Policy) {Try {Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $Policy.Id -State Enabled -ErrorAction StopWrite-Output "Conditional Access policy 'Block Weekend Access' is now enabled"} Catch {Write-Error ("Failed to enable 'Block Weekend Access' policy: {0}" -f $_.Exception.Message)}}# See if a site list is available to add details of each user to$UpdateList = $false$Uri = "https://office365itpros.sharepoint.com/sites/O365ExchPro"$SiteId = $Uri.Split('//')[1].split("/")[0] + ":/sites/" + $Uri.Split('//')[1].split("/")[2]$Site = Get-MgSite -SiteId $SiteId$List = Get-MgSiteList -SiteId $Site.Id -Filter "displayName eq 'Planner Task Burndown'"If ($List) {$UpdateList = $true}# Extract data that we want to report on[array]$PlanData = $UserTasks | Where-Object {$_.Plan -eq 'MAC Tasks'}$NewItemParameters = @{fields = @{Title = (New-Guid).GuidPlan = $PlanData[0].PlanUPN = $PlanData[0].UserEmailUsername = $PlanData[0].UserTasks = $PlanData.countRunDate = Get-Date -Format 'dd-MMM-yyyy HH:mm'Averagedaysoutstanding = [math]::Round(($PlanData | Measure-Object -Property 'Days Outstanding' -Average).Average, 2)}}Try {$NewItem = New-MgSiteListItem -SiteId $Site.Id -ListId $List.Id -BodyParameter $NewItemParameters -ErrorAction StopWrite-Output ("Created new list item with ID {0}" -f $NewItem.Id)} Catch {Write-Error ("Failed to create new list item: {0}" -f $_.Exception.Message)}Write-Host ("File {0} is now archived" -f $File.Name) -ForegroundColor Red[array]$Domains = Get-AcceptedDomain | Select-Object -ExpandProperty DomainName$Operations = "MemberAdded"$RecordType = "MicrosoftTeams"Write-Output "Searching audit log for Microsoft Teams MemberAdded events between $StartDate and $EndDate..."[array]$Records = Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Formatted `-SessionCommand ReturnLargeSet -ResultSize 5000 -Operations $Operations -RecordType $RecordType$Records = $Records | Sort-Object Identity -UniqueIf ($Records) {Write-Host ("{0} audit records found for {1} operations between {2} and {3}" -f $Records.Count, $Operations, $StartDate, $EndDate)} Else {Write-Host ("No audit records found for {0} operations between {1} and {2}" -f $Operations, $StartDate, $EndDate)Break}$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Rec in $Records) {$AuditData = $Rec.AuditData | ConvertFrom-JsonIf ($AuditData.CommunicationType -eq "OneOnOne") {$ForeignDomain = $AuditData.ParticipantInfo.ParticipatingDomains | Where-Object {$_ -notin $Domains}$ForeignTenantId = $AuditData.ParticipantInfo.ParticipatingTenantIds | Where-Object {$_ -ne $AuditData.OrganizationId}$ReportLine = [PSCustomObject][Ordered]@{TimeStamp = Get-Date ($AuditData.CreationTime) -format 'dd-MMM-yyyy HH:mm'User = $AuditData.UserIdOperation = $AuditData.OperationUserId = $AuditData.UserKeyChatThreadId = $AuditData.ChatThreadIdMembersAdded = $AuditData.Members.UPN -join ", "HasForeignTenantUsers = $AuditData.ParticipantInfo.HasForeignTenantUsersForeignDomain = $ForeignDomainForeignTenantId = $ForeignTenantId}$Report.Add($ReportLine)}}$Report = $Report | Sort-Object {$_.TimeStamp -as [datetime]} -Descending$UserId = "59e09287-ac1b-4ff7-80a3-08d0d1eed939"$UserInfo = @{}$UserData = @{}$UserData.Add("id", $UserId)$UserData.Add("tenantId", $TenantId)$UserInfo.Add("user", $UserData)$ChatId = "19:110ef7a6-82ac-41c0-b0d1-86d88fc566b6_59e09287-ac1b-4ff7-80a3-08d0d1eed939@unq.gbl.spaces"$Uri = ("https://graph.microsoft.com/beta/chats/{0}/removeAllAccessForUser" -f $ChatId)Try {# Requires Chat.ReadWrite.All permissionRemove-MgChatAccessForUser -ChatId $ChatId -BodyParameter $UserInfo -ErrorAction StopWrite-Output ("Removed user access from chat {0}" -f $ChatId)} Catch {Write-Error ("Failed to remove user access from chat {0}: {1}" -f $ChatId, $_.Exception.Message)}# Code to figure out how the progress team members are making in burning out assigned tasks in Planner.If ($List) {$UpdateList = $true# Get list items and load them into a PowerShell list[array]$ListItems = Get-MgSiteListItem -ListId $List.Id -SiteId $Site.Id -ExpandProperty "fields(`$select=id,title,plan,upn,username,Tasks,RunDate,Averagedaysoutstanding,ObjectId)" -PageSize 999 -All$ItemData = [System.Collections.Generic.List[Object]]::new()ForEach ($Item in $ListItems.fields) {$ReportLine = [PSCustomObject] @{Id = $Item.IdItemId = $Item.additionalProperties.TitlePlan = $Item.additionalProperties.PlanUPN = $Item.additionalProperties.UPNUserName = $Item.additionalProperties.UsernameTasks = $Item.additionalProperties.TasksRundate = $Item.additionalProperties.RunDateAveragedaysoutstanding = $Item.additionalProperties.AveragedaysoutstandingObjectId = $Item.additionalProperties.ObjectId}$ItemData.Add($ReportLine)}}# Processing for individual users[array]$UserItems = $ItemData | Where-Object {$_.ObjectId -eq $Id -and $_.Plan -eq 'MAC Tasks'}# Add current stats for the user from Planner$CurrentData = [PSCustomObject] @{Title = 'new Guid'ItemId = '999'Plan = $NewItemParameters.Fields['Plan']UPN = $NewItemParameters.Fields['UPN']UserName = $NewItemParameters.Fields['Username']Tasks = $NewItemParameters.Fields['Tasks']Rundate = $NewItemParameters.Fields['RunDate']Averagedaysoutstanding = $NewItemParameters.Fields['Averagedaysoutstanding']ObjectId = $NewItemParameters.Fields['ObjectId']}$UserItems += $CurrentData# Role assignments report[array]$DirectoryRoles = Get-MgRoleManagementDirectoryRoleDefinition -All | Sort-Object DisplayNameForEach ($Role in $DirectoryRoles) {$RoleId = $Role.Id[array]$RoleMembers = Get-MgRoleManagementDirectoryRoleAssignment -Filter "roleDefinitionId eq '$RoleId'" -ExpandProperty PrincipalIf ($RoleMembers) {$RoleName = $Role.DisplayNameForEach ($Member in $RoleMembers) {Switch ($Member.Principal.additionalProperties["@odata.type"]) {"#microsoft.graph.user" {Write-Output ("{0} is a user member of the {1} role" -f $Member.Principal.additionalProperties.displayName, $RoleName)}"#microsoft.graph.servicePrincipal" {Write-Output ("{0} is a service principal member of the {1} role" -f $Member.Principal.additionalProperties.displayName, $RoleName)}"#microsoft.graph.group" {Write-Output ("{0} is a group member of the {1} role" -f $Member.Principal.additionalProperties.displayName, $RoleName)}Default {Write-Output ("A principal with ID {0} and type {1} is a member of the {2} role" -f $Member.PrincipalId, $Member.Principal.additionalProperties["@odata.type"], $RoleName)}}}}}ForEach ($Label in $TenantLabels) {If ($Label.Sublabels) {$TenantLabels += $Label.Sublabels}}$TenantLabels = $TenantLabels | Sort-Object Id -UniqueForEach ($Label in $UserLabels) {If ($Label.Parent.Id) {$UserLabels += $Label.Parent}}$UserLabels = $UserLabels | Sort-Object Name -Unique
Attribution
Author
Office365itpros