Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CIPP-Permissions.json
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,11 @@
"Name": "UserAuthenticationMethod.ReadWrite",
"Description": "Allows the app to read and write your authentication methods, including phone numbers and Authenticator app settings.This does not allow the app to see secret information like your passwords, or to sign-in or otherwise use your authentication methods."
},
{
"Id": "424b07a8-1209-4d17-9fe4-9018a93a1024",
"Name": "TeamsTelephoneNumber.ReadWrite.All",
"Description": "Allows the app to read and modify your tenant's acquired telephone number details on behalf of the signed-in admin user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc."
},
{
"Id": "b7887744-6746-4312-813d-72daeaee7e2d",
"Name": "UserAuthenticationMethod.ReadWrite.All",
Expand Down Expand Up @@ -697,6 +702,11 @@
"Name": "User.ReadWrite.All",
"Description": "Allows the app to read and update user profiles without a signed in user."
},
{
"Id": "0a42382f-155c-4eb1-9bdc-21548ccaa387",
"Name": "TeamsTelephoneNumber.ReadWrite.All",
"Description": "Allows the app to read your tenant's acquired telephone number details, without a signed-in user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc."
},
{
"Id": "50483e42-d915-4231-9639-7fdb7fd190e5",
"Name": "UserAuthenticationMethod.ReadWrite.All",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using namespace System.Net

Function Invoke-ListMessageTrace {
function Invoke-ListMessageTrace {
<#
.FUNCTIONALITY
Entrypoint
Expand Down Expand Up @@ -65,11 +65,11 @@ Function Invoke-ListMessageTrace {
MessageTraceId = $Request.Body.ID
RecipientAddress = $Request.Body.recipient
}
New-ExoRequest -TenantId $TenantFilter -Cmdlet 'Get-MessageTraceDetail' -CmdParams $CmdParams | Select-Object @{ Name = 'Date'; Expression = { $_.Date.ToString('u') } }, Event, Action, Detail
New-ExoRequest -TenantId $TenantFilter -Cmdlet 'Get-MessageTraceDetailV2' -CmdParams $CmdParams | Select-Object @{ Name = 'Date'; Expression = { $_.Date.ToString('u') } }, Event, Action, Detail
} else {
Write-Information ($SearchParams | ConvertTo-Json)

New-ExoRequest -TenantId $TenantFilter -Cmdlet 'Get-MessageTrace' -CmdParams $SearchParams | Select-Object MessageTraceId, Status, Subject, RecipientAddress, SenderAddress, @{ Name = 'Received'; Expression = { $_.Received.ToString('u') } }, FromIP, ToIP
New-ExoRequest -TenantId $TenantFilter -Cmdlet 'Get-MessageTraceV2' -CmdParams $SearchParams | Select-Object MessageTraceId, Status, Subject, RecipientAddress, SenderAddress, @{ Name = 'Received'; Expression = { $_.Received.ToString('u') } }, FromIP, ToIP
Write-LogMessage -headers $Request.Headers -API $APIName -tenant $($TenantFilter) -message 'Executed message trace' -Sev 'Info'

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ function Invoke-CIPPOffboardingJob {
$_.Exception.Message
}
}
{ $_.RemoveTeamsPhoneDID } {
try {
Remove-CIPPUserTeamsPhoneDIDs -userid $userid -username $username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName
} catch {
$_.Exception.Message
}
}
{ $_.RemoveLicenses -eq $true } {
Remove-CIPPLicense -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Schedule
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using namespace System.Net

function Invoke-DeleteSharepointSite {
<#
.FUNCTIONALITY
Entrypoint
.ROLE
Sharepoint.Site.ReadWrite
#>
[CmdletBinding()]
param($Request, $TriggerMetadata)

$APIName = $Request.Params.CIPPEndpoint
$Headers = $Request.Headers
Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug'

# Interact with query parameters or the body of the request.
$TenantFilter = $Request.Body.tenantFilter
$SiteId = $Request.Body.SiteId

try {
# Validate required parameters
if (-not $SiteId) {
throw "SiteId is required"
}
if (-not $TenantFilter) {
throw "TenantFilter is required"
}

# Validate SiteId format (GUID)
if ($SiteId -notmatch '^(\{)?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(\})?$') {
throw "SiteId must be a valid GUID"
}

$SharePointInfo = Get-SharePointAdminLink -Public $false -tenantFilter $TenantFilter

# Get site information using SharePoint admin API
$SiteInfoUri = "$($SharePointInfo.AdminUrl)/_api/SPO.Tenant/sites('$SiteId')"

# Add the headers that SharePoint REST API expects
$ExtraHeaders = @{
'accept' = 'application/json'
'content-type' = 'application/json'
'odata-version' = '4.0'
}

$SiteInfo = New-GraphGETRequest -scope "$($SharePointInfo.AdminUrl)/.default" -uri $SiteInfoUri -tenantid $TenantFilter -extraHeaders $ExtraHeaders

if (-not $SiteInfo) {
throw "Could not retrieve site information from SharePoint Admin API"
}

# Determine if site is group-connected based on GroupId
$IsGroupConnected = $SiteInfo.GroupId -and $SiteInfo.GroupId -ne "00000000-0000-0000-0000-000000000000"

if ($IsGroupConnected) {
# Use GroupSiteManager/Delete for group-connected sites
$body = @{
siteUrl = $SiteInfo.Url
}
$DeleteUri = "$($SharePointInfo.AdminUrl)/_api/GroupSiteManager/Delete"
} else {
# Use SPSiteManager/delete for regular sites
$body = @{
siteId = $SiteId
}
$DeleteUri = "$($SharePointInfo.AdminUrl)/_api/SPSiteManager/delete"
}

# Execute the deletion
$DeleteResult = New-GraphPOSTRequest -scope "$($SharePointInfo.AdminUrl)/.default" -uri $DeleteUri -body (ConvertTo-Json -Depth 10 -InputObject $body) -tenantid $TenantFilter -extraHeaders $ExtraHeaders

$SiteTypeMsg = if ($IsGroupConnected) { "group-connected" } else { "regular" }
$Results = "Successfully initiated deletion of $SiteTypeMsg SharePoint site with ID $SiteId, this process can take some time to complete in the background"

Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -sev Info
$StatusCode = [HttpStatusCode]::OK

} catch {
$ErrorMessage = Get-CippException -Exception $_
$Results = "Failed to delete SharePoint site with ID $SiteId. Error: $($ErrorMessage.NormalizedError)"
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -sev Error -LogData $ErrorMessage
$StatusCode = [HttpStatusCode]::InternalServerError
}

# Associate values to output bindings
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = $StatusCode
Body = @{ 'Results' = $Results }
})
}
23 changes: 18 additions & 5 deletions Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function New-GraphGetRequest {
$RetryCount = 0
$MaxRetries = 3
$RequestSuccessful = $false
Write-Host "This is attempt $($RetryCount + 1) of $MaxRetries"
Write-Information "GET [ $nextURL ] | tenant: $tenantid | attempt: $($RetryCount + 1) of $MaxRetries"
do {
try {
$GraphRequest = @{
Expand Down Expand Up @@ -117,11 +117,24 @@ function New-GraphGetRequest {
} catch {
$ShouldRetry = $false
$WaitTime = 0

try {
$Message = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).error.message
$MessageObj = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
if ($MessageObj.error) {
$MessageObj | Add-Member -NotePropertyName 'url' -NotePropertyValue $nextURL -Force
$Message = $MessageObj.error.message -ne '' ? $MessageObj.error.message : $MessageObj.error.code
}
} catch { $Message = $null }
if ($Message -eq $null) { $Message = $($_.Exception.Message) }

if ([string]::IsNullOrEmpty($Message)) {
$Message = $($_.Exception.Message)
$MessageObj = @{
error = @{
code = $_.Exception.GetType().FullName
message = $Message
url = $nextURL
}
}
}

# Check for 429 Too Many Requests
if ($_.Exception.Response.StatusCode -eq 429) {
Expand All @@ -147,7 +160,7 @@ function New-GraphGetRequest {
} else {
# Final failure - update tenant error tracking and throw
if ($Message -ne 'Request not applicable to target tenant.' -and $Tenant) {
$Tenant.LastGraphError = $Message
$Tenant.LastGraphError = [string]($MessageObj | ConvertTo-Json -Compress)
if ($Tenant.PSObject.Properties.Name -notcontains 'GraphErrorCount') {
$Tenant | Add-Member -MemberType NoteProperty -Name 'GraphErrorCount' -Value 0 -Force
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ function Get-GraphRequestList {
$SingleTenantThreshold = 8000
Write-Information "Tenant: $TenantFilter"
$TableName = ('cache{0}' -f ($Endpoint -replace '[^A-Za-z0-9]'))[0..62] -join ''
Write-Information "Table: $TableName"
$Endpoint = $Endpoint -replace '^/', ''
$DisplayName = ($Endpoint -split '/')[0]

Expand Down Expand Up @@ -123,7 +122,6 @@ function Get-GraphRequestList {
}
$GraphQuery.Query = $ParamCollection.ToString()
$PartitionKey = Get-StringHash -String (@($Endpoint, $ParamCollection.ToString(), 'v2') -join '-')
Write-Information "PK: $PartitionKey"

# Perform $count check before caching
$Count = 0
Expand Down Expand Up @@ -174,7 +172,7 @@ function Get-GraphRequestList {
Write-Information "Total results (`$count): $Count"
}
}
Write-Information ( 'GET [ {0} ]' -f $GraphQuery.ToString())
#Write-Information ( 'GET [ {0} ]' -f $GraphQuery.ToString())

try {
if ($QueueId) {
Expand All @@ -196,7 +194,7 @@ function Get-GraphRequestList {
}
$Rows = Get-CIPPAzDataTableEntity @Table -Filter $Filter
$Type = 'Cache'
Write-Information "Cached: $(($Rows | Measure-Object).Count) rows (Type: $($Type))"
Write-Information "Table: $TableName | PK: $PartitionKey | Cached: $(($Rows | Measure-Object).Count) rows (Type: $($Type))"
$QueueReference = '{0}-{1}' -f $TenantFilter, $PartitionKey
$RunningQueue = Invoke-ListCippQueue -Reference $QueueReference | Where-Object { $_.Status -notmatch 'Completed' -and $_.Status -notmatch 'Failed' }
}
Expand Down
18 changes: 18 additions & 0 deletions Modules/CIPPCore/Public/PermissionsTranslator.json
Original file line number Diff line number Diff line change
Expand Up @@ -5353,5 +5353,23 @@
"userConsentDescription": "Access Microsoft Teams and Skype for Business data as the signed in user",
"userConsentDisplayName": "Access Microsoft Teams and Skype for Business data based on the user's role membership",
"value": "OnPremDirectorySynchronization.ReadWrite.All"
},
{
"description": "Read and Modify Tenant-Acquired Telephone Number Details",
"displayName": "Read and Modify Tenant-Acquired Telephone Number Details",
"id": "424b07a8-1209-4d17-9fe4-9018a93a1024",
"Origin": "Delegated",
"userConsentDescription": "Allows the app to read and modify your tenant's acquired telephone number details on behalf of the signed-in admin user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc.",
"userConsentDisplayName": "Allows the app to read and modify your tenant's acquired telephone number details on behalf of the signed-in admin user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc.",
"value": "TeamsTelephoneNumber.ReadWrite.All"
},
{
"description": "Read and Modify Tenant-Acquired Telephone Number Details",
"displayName": "Read and Modify Tenant-Acquired Telephone Number Details",
"id": "0a42382f-155c-4eb1-9bdc-21548ccaa387",
"Origin": "Application",
"userConsentDescription": "Allows the app to read your tenant's acquired telephone number details, without a signed-in user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc.",
"userConsentDisplayName": "Allows the app to read your tenant's acquired telephone number details, without a signed-in user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc.",
"value": "TeamsTelephoneNumber.ReadWrite.All"
}
]
98 changes: 98 additions & 0 deletions Modules/CIPPCore/Public/Remove-CIPPUserTeamsPhoneDIDs.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using namespace System.Net
using namespace System.Collections.Generic

function Remove-CIPPUserTeamsPhoneDIDs {
[CmdletBinding()]
param (
$Headers,
[parameter(Mandatory = $true)]
[string]$UserID,
[string]$Username,
$APIName = 'Remove User Teams Phone DIDs',
[parameter(Mandatory = $true)]
$TenantFilter
)

try {

# Set Username to UserID if not provided
if ([string]::IsNullOrEmpty($Username)) {
$Username = $UserID
}

# Initialize collections for results
$Results = [List[string]]::new()
$SuccessCount = 0
$ErrorCount = 0

# Get all tenant DIDs
$TeamsPhoneDIDs = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/admin/teams/telephoneNumberManagement/numberAssignments" -tenant $TenantFilter

if (-not $TeamsPhoneDIDs -or $TeamsPhoneDIDs.Count -eq 0) {
$Result = "No Teams Phone DIDs found in tenant"
$Results.Add($Result)
return $Results.ToArray()
}

# Filter DIDs assigned to the specific user
$UserDIDs = $TeamsPhoneDIDs | Where-Object { $_.assignmentTargetId -eq $UserID -and $_.assignmentStatus -ne 'unassigned' }

if (-not $UserDIDs -or $UserDIDs.Count -eq 0) {
$Result = "No Teams Phone DIDs found assigned to user: '$Username' - '$UserID'"
$Results.Add($Result)
return $Results.ToArray()
}

# Prepare bulk requests for all DIDs
$RemoveRequests = foreach ($DID in $UserDIDs) {
@{
id = $DID.telephoneNumber
method = 'POST'
url = "admin/teams/telephoneNumberManagement/numberAssignments/unassignNumber"
body = @{
telephoneNumber = $DID.telephoneNumber
numberType = $DID.numberType
}
}
}

# Execute bulk request
$RemoveResults = New-GraphBulkRequest -tenantid $TenantFilter -requests @($RemoveRequests)

# Process results
$RemoveResults | ForEach-Object {
$PhoneNumber = $_.id

if ($_.status -eq 204) {
$SuccessResult = "Successfully removed Teams Phone DID: '$PhoneNumber' from: '$Username' - '$UserID'"
Write-LogMessage -headers $Headers -API $APIName -message $SuccessResult -Sev 'Info' -tenant $TenantFilter
$Results.Add($SuccessResult)
$SuccessCount++
} else {
$ErrorMessage = if ($_.body.error.message) {
$_.body.error.message
} else {
"HTTP Status: $($_.status)"
}

$ErrorResult = "Failed to remove Teams Phone DID: '$PhoneNumber' from: '$Username' - '$UserID'. Error: $ErrorMessage"
Write-LogMessage -headers $Headers -API $APIName -message $ErrorResult -Sev 'Error' -tenant $TenantFilter
$Results.Add($ErrorResult)
$ErrorCount++
}
}

# Add summary result
$SummaryResult = "Completed processing $($UserDIDs.Count) DIDs for user '$Username': $SuccessCount successful, $ErrorCount failed"
Write-LogMessage -headers $Headers -API $APIName -message $SummaryResult -Sev 'Info' -tenant $TenantFilter
$Results.Add($SummaryResult)

return $Results.ToArray()

} catch {
$ErrorMessage = Get-CippException -Exception $_
$Result = "Failed to process Teams Phone DIDs removal for: '$Username' - '$UserID'. Error: $($ErrorMessage.NormalizedError)"
Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage
throw $Result
}
}
10 changes: 9 additions & 1 deletion Modules/CIPPCore/Public/SAMManifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,14 @@
{
"id": "b7887744-6746-4312-813d-72daeaee7e2d",
"type": "Scope"
},
{
"id": "424b07a8-1209-4d17-9fe4-9018a93a1024",
"type": "Scope"
},
{
"id": "0a42382f-155c-4eb1-9bdc-21548ccaa387",
"type": "Role"
}
]
},
Expand Down Expand Up @@ -643,4 +651,4 @@
]
}
]
}
}
2 changes: 1 addition & 1 deletion version_latest.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8.4.0
8.4.2