Skip to content

Commit 82e1650

Browse files
authored
Merge pull request #1064 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 01969a2 + 23c0c83 commit 82e1650

26 files changed

Lines changed: 350 additions & 131 deletions

Modules/CIPPCore/Public/Get-CIPPDrift.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ function Get-CIPPDrift {
4949
$IntuneTemplatesByGuid = @{}
5050
$AllIntuneTemplates = foreach ($RawTemplate in $RawIntuneTemplates) {
5151
try {
52-
$JSONData = $RawTemplate.JSON | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue
53-
$data = $JSONData.RAWJson | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue
52+
$JSONData = $RawTemplate.JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue
53+
$data = $JSONData.RAWJson | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue
5454
$data | Add-Member -NotePropertyName 'displayName' -NotePropertyValue $JSONData.Displayname -Force
5555
$data | Add-Member -NotePropertyName 'description' -NotePropertyValue $JSONData.Description -Force
5656
$data | Add-Member -NotePropertyName 'Type' -NotePropertyValue $JSONData.Type -Force
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
function New-TeamsRequestV2 {
2+
<#
3+
.SYNOPSIS
4+
Talks to the Teams admin ConfigAPI (api.interfaces.records.teams.microsoft.com)
5+
directly, instead of the MicrosoftTeams PowerShell module.
6+
7+
.DESCRIPTION
8+
The MicrosoftTeams module routes policy-definition writes through the legacy
9+
/Skype.Policy/tenants/policies surface, which returns 40301 Forbidden under CSP/GDAP
10+
(for both delegate-with-Teams-Admin and app-only-with-Global-Admin tokens). The Teams
11+
admin center (ACMS) instead uses /Skype.Policy/configurations/{Type}/configuration/{Identity},
12+
which authorizes fine with the roles CIPP already has. This helper speaks that surface.
13+
14+
All HTTP goes through Invoke-CIPPRestMethod (pooled CIPP.CIPPRestClient) so gzip
15+
responses are decompressed and JSON is deserialized consistently across platforms
16+
(raw Invoke-RestMethod does NOT decompress gzip in the Linux worker, which yields
17+
garbage/null objects and unreadable error bodies).
18+
19+
Operations:
20+
Get -> GET configurations/{Type}/configuration/{Identity} (single object)
21+
Get -ListAll -> GET configurations/{Type} (all instances, array)
22+
Set -> PUT configurations/{Type}/configuration/{Identity} (MERGE; only changed props)
23+
New -> PUT configurations/{Type}/configuration/{Identity} (create named policy) [best-effort]
24+
Remove -> DELETE configurations/{Type}/configuration/{Identity} [best-effort]
25+
26+
.PARAMETER TenantFilter
27+
Target tenant (GUID or default domain).
28+
29+
.PARAMETER Type
30+
ConfigAPI type (e.g. 'TeamsMeetingPolicy'), a cmdlet noun, or a full cmdlet name
31+
(e.g. 'Set-CsTenantFederationConfiguration'); the verb is stripped and known
32+
noun->type aliases are applied automatically.
33+
34+
.PARAMETER Action
35+
Get (default) | Set | New | Remove.
36+
37+
.PARAMETER Identity
38+
Policy instance identity. Default 'Global'. (e.g. 'Tag:Default' or a custom name.)
39+
40+
.PARAMETER Parameters
41+
Hashtable of properties to write (Set/New). Only these are sent (merge).
42+
43+
.PARAMETER ListAll
44+
Get: return every instance of the type (array) instead of one identity.
45+
46+
.PARAMETER AsApp
47+
Use an app-only (client_credentials) token instead of the delegate token.
48+
49+
.PARAMETER NoRead
50+
Set: skip the read-for-Key step and PUT only the bare changed props (ACMS-style).
51+
Faster; safe for flat/Host-authority types and federation. Default is read-modify-write
52+
(adds the Key envelope when the type carries one) for maximum compatibility.
53+
54+
.PARAMETER UseServiceDiscovery
55+
Resolve the per-tenant ConfigApi host + X-MS-Forest via Teams.Tenant/tenants and use
56+
them (and, for federation/ACS types, the OcsPowershellWebservice target headers).
57+
58+
.EXAMPLE
59+
New-TeamsRequestV2 -TenantFilter $t -Type TeamsMeetingPolicy -Action Set -Parameters @{ AllowAnonymousUsersToJoinMeeting = $false }
60+
61+
.FUNCTIONALITY
62+
Internal
63+
#>
64+
[CmdletBinding()]
65+
param(
66+
[Parameter(Mandatory)] $TenantFilter,
67+
[Parameter(Mandatory)] [string] $Type,
68+
[ValidateSet('Get', 'Set', 'New', 'Remove')] [string] $Action = 'Get',
69+
[string] $Identity = 'Global',
70+
[hashtable] $Parameters = @{},
71+
[switch] $ListAll,
72+
[switch] $AsApp,
73+
[switch] $NoRead,
74+
[switch] $UseServiceDiscovery
75+
)
76+
77+
# ---- cmdlet-noun -> ConfigAPI type aliases (noun != type) ----
78+
$TypeAliases = @{
79+
'TenantFederationConfiguration' = 'TenantFederationSettings'
80+
}
81+
# types backed by the legacy OcsPowershellWebservice (need target-uri headers via discovery)
82+
$FederationTypes = @('TenantFederationSettings', 'TeamsAcsFederationConfiguration')
83+
84+
# normalize Type: strip Get-/Set-/New-/Remove-Cs prefix, then alias
85+
$ConfigType = $Type -replace '^(Get|Set|New|Remove|Grant|Revoke)-Cs', ''
86+
if ($TypeAliases.ContainsKey($ConfigType)) { $ConfigType = $TypeAliases[$ConfigType] }
87+
88+
# ---- token ----
89+
$TokenSplat = @{ tenantid = $TenantFilter; scope = '48ac35b8-9aa8-4d74-927d-1f4a14a0b239/.default' }
90+
if ($AsApp) { $TokenSplat['AsApp'] = $true }
91+
$TeamsToken = (Get-GraphToken @TokenSplat).Authorization -replace 'Bearer '
92+
93+
# ---- endpoint + forest (service discovery optional) ----
94+
$ApiHost = 'api.interfaces.records.teams.microsoft.com'
95+
$Forest = $null
96+
$AdminServiceEndpoint = $null
97+
$AdminDomain = $null
98+
if ($UseServiceDiscovery) {
99+
try {
100+
$disc = Invoke-CIPPRestMethod -Uri "https://$ApiHost/Teams.Tenant/tenants" -Method GET `
101+
-Headers @{ Authorization = "Bearer $TeamsToken"; Accept = 'application/json' }
102+
if ($disc.serviceDiscovery.Endpoints.ConfigApiEndpoint) { $ApiHost = $disc.serviceDiscovery.Endpoints.ConfigApiEndpoint }
103+
$Forest = $disc.serviceDiscovery.Headers.'X-MS-Forest'
104+
$AdminServiceEndpoint = $disc.serviceDiscovery.Endpoints.AdminServiceEndpoint
105+
$AdminDomain = ($disc.verifiedDomains | Where-Object { $_.name -like '*.onmicrosoft.com' -and $_.name -notlike '*.*.onmicrosoft.com' } | Select-Object -First 1).name
106+
} catch {
107+
Write-Verbose "Service discovery failed, using defaults: $($_.Exception.Message)"
108+
}
109+
}
110+
$Base = "https://$ApiHost/Skype.Policy/configurations/$ConfigType"
111+
112+
# ---- headers ----
113+
$Headers = @{
114+
Authorization = "Bearer $TeamsToken"
115+
Accept = 'application/json'
116+
'x-authz-scope' = 'tenant'
117+
'x-ms-correlation-id' = (New-Guid).Guid
118+
}
119+
if ($Forest) { $Headers['x-ms-forest'] = $Forest }
120+
if ($ConfigType -in $FederationTypes -and $AdminServiceEndpoint) {
121+
$Headers['x-ms-target-uri'] = "https://$AdminServiceEndpoint/OcsPowershellWebservice"
122+
$Headers['x-ms-tenant-id'] = $TenantFilter
123+
}
124+
$Query = if ($ConfigType -in $FederationTypes -and $AdminDomain) { "?adminDomain=$AdminDomain" } else { '' }
125+
126+
switch ($Action) {
127+
128+
'Get' {
129+
$Uri = if ($ListAll) { "$Base$Query" } else { "$Base/configuration/$Identity$Query" }
130+
return Invoke-CIPPRestMethod -Uri $Uri -Method GET -Headers $Headers
131+
}
132+
133+
'Set' {
134+
$Uri = "$Base/configuration/$Identity$Query"
135+
136+
# build body of only the changed props (skip control keys)
137+
$Body = [ordered]@{}
138+
foreach ($k in $Parameters.Keys) {
139+
if ($k -in @('Identity', 'ErrorAction', 'Confirm', 'WhatIf', 'Verbose', 'Debug')) { continue }
140+
$Body[$k] = $Parameters[$k]
141+
}
142+
143+
if (-not $NoRead) {
144+
# read-modify-write: include the Key envelope when the type carries one (max compat)
145+
try {
146+
$Current = Invoke-CIPPRestMethod -Uri $Uri -Method GET -Headers $Headers
147+
if ($Current -and ($Current.PSObject.Properties.Name -contains 'Key')) {
148+
$Merged = [ordered]@{ Identity = $Identity; Key = $Current.Key }
149+
foreach ($k in $Body.Keys) { $Merged[$k] = $Body[$k] }
150+
$Body = $Merged
151+
}
152+
} catch {
153+
Write-Verbose "Pre-read failed ($($_.Exception.Message)); sending bare props."
154+
}
155+
}
156+
157+
$Json = $Body | ConvertTo-Json -Depth 25 -Compress
158+
$StatusCode = $null
159+
$RespBody = Invoke-CIPPRestMethod -Uri $Uri -Method PUT -Body $Json -ContentType 'application/json' `
160+
-Headers $Headers -SkipHttpErrorCheck -StatusCodeVariable StatusCode
161+
if ([int]$StatusCode -ge 400) {
162+
$Detail = if ($RespBody -is [string]) { $RespBody } elseif ($null -ne $RespBody) { $RespBody | ConvertTo-Json -Compress -Depth 10 } else { '' }
163+
throw "Teams ConfigApi Set $ConfigType/$Identity failed: $StatusCode $Detail"
164+
}
165+
return [pscustomobject]@{ Type = $ConfigType; Identity = $Identity; StatusCode = [int]$StatusCode }
166+
}
167+
168+
'New' {
169+
# best-effort: create a named policy by PUTting the props at a new identity
170+
$Uri = "$Base/configuration/$Identity$Query"
171+
$Body = [ordered]@{ Identity = $Identity }
172+
foreach ($k in $Parameters.Keys) {
173+
if ($k -in @('Identity', 'ErrorAction', 'Confirm', 'WhatIf', 'Verbose', 'Debug')) { continue }
174+
$Body[$k] = $Parameters[$k]
175+
}
176+
$Json = $Body | ConvertTo-Json -Depth 25 -Compress
177+
$StatusCode = $null
178+
$RespBody = Invoke-CIPPRestMethod -Uri $Uri -Method PUT -Body $Json -ContentType 'application/json' `
179+
-Headers $Headers -SkipHttpErrorCheck -StatusCodeVariable StatusCode
180+
if ([int]$StatusCode -ge 400) {
181+
$Detail = if ($RespBody -is [string]) { $RespBody } elseif ($null -ne $RespBody) { $RespBody | ConvertTo-Json -Compress -Depth 10 } else { '' }
182+
throw "Teams ConfigApi New $ConfigType/$Identity failed: $StatusCode $Detail"
183+
}
184+
return [pscustomobject]@{ Type = $ConfigType; Identity = $Identity; StatusCode = [int]$StatusCode }
185+
}
186+
187+
'Remove' {
188+
$Uri = "$Base/configuration/$Identity$Query"
189+
$StatusCode = $null
190+
$RespBody = Invoke-CIPPRestMethod -Uri $Uri -Method DELETE -Headers $Headers -SkipHttpErrorCheck -StatusCodeVariable StatusCode
191+
if ([int]$StatusCode -ge 400) {
192+
$Detail = if ($RespBody -is [string]) { $RespBody } elseif ($null -ne $RespBody) { $RespBody | ConvertTo-Json -Compress -Depth 10 } else { '' }
193+
throw "Teams ConfigApi Remove $ConfigType/$Identity failed: $StatusCode $Detail"
194+
}
195+
return [pscustomobject]@{ Type = $ConfigType; Identity = $Identity; StatusCode = [int]$StatusCode }
196+
}
197+
}
198+
}

Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ function Invoke-CIPPDBCacheCollection {
150150
'CsExternalAccessPolicy'
151151
'CsTenantFederationConfiguration'
152152
'CsTeamsMessagingPolicy'
153+
'CsTeamsMessagingConfiguration'
153154
'CsTeamsAppPermissionPolicy'
154155
'Teams'
155156
'TeamsActivity'

Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsExternalAccessPolicy.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function Set-CIPPDBCacheCsExternalAccessPolicy {
2424
try {
2525
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Teams External Access Policy' -sev Debug
2626

27-
$ExternalAccess = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Get-CsExternalAccessPolicy' -CmdParams @{ Identity = 'Global' }
27+
$ExternalAccess = New-TeamsRequestV2 -TenantFilter $TenantFilter -Type 'ExternalAccessPolicy' -Action Get -Identity 'Global'
2828

2929
if ($ExternalAccess) {
3030
$Data = @($ExternalAccess)

Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsAppPermissionPolicy.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function Set-CIPPDBCacheCsTeamsAppPermissionPolicy {
2424
try {
2525
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Teams App Permission Policies' -sev Debug
2626

27-
$AppPermissionPolicies = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Get-CsTeamsAppPermissionPolicy'
27+
$AppPermissionPolicies = New-TeamsRequestV2 -TenantFilter $TenantFilter -Type 'TeamsAppPermissionPolicy' -Action Get -ListAll
2828

2929
if ($AppPermissionPolicies) {
3030
$Data = @($AppPermissionPolicies)

Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsClientConfiguration.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function Set-CIPPDBCacheCsTeamsClientConfiguration {
2525
try {
2626
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Teams Client Configuration' -sev Debug
2727

28-
$ClientConfig = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Get-CsTeamsClientConfiguration' -CmdParams @{ Identity = 'Global' }
28+
$ClientConfig = New-TeamsRequestV2 -TenantFilter $TenantFilter -Type 'TeamsClientConfiguration' -Action Get -Identity 'Global'
2929

3030
if ($ClientConfig) {
3131
$Data = @($ClientConfig)

Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMeetingPolicy.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function Set-CIPPDBCacheCsTeamsMeetingPolicy {
2424
try {
2525
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Teams Meeting Policy' -sev Debug
2626

27-
$MeetingPolicy = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Get-CsTeamsMeetingPolicy' -CmdParams @{ Identity = 'Global' }
27+
$MeetingPolicy = New-TeamsRequestV2 -TenantFilter $TenantFilter -Type 'TeamsMeetingPolicy' -Action Get -Identity 'Global'
2828

2929
if ($MeetingPolicy) {
3030
$Data = @($MeetingPolicy)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
function Set-CIPPDBCacheCsTeamsMessagingConfiguration {
2+
<#
3+
.SYNOPSIS
4+
Caches the Teams Messaging Configuration (Global)
5+
6+
.DESCRIPTION
7+
Calls the Teams ConfigAPI (TeamsMessagingConfiguration) via New-TeamsRequestV2 and
8+
writes the result into the CippReportingDB under Type 'CsTeamsMessagingConfiguration'.
9+
Holds the org-wide message-safety settings (FileTypeCheck, UrlReputationCheck,
10+
ContentBasedPhishingCheck, ReportIncorrectSecurityDetections, etc.).
11+
12+
.PARAMETER TenantFilter
13+
The tenant to cache the messaging configuration for
14+
15+
.PARAMETER QueueId
16+
The queue ID to update with total tasks (optional)
17+
#>
18+
[CmdletBinding()]
19+
param(
20+
[Parameter(Mandatory = $true)]
21+
[string]$TenantFilter,
22+
[string]$QueueId
23+
)
24+
25+
try {
26+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Teams Messaging Configuration' -sev Debug
27+
28+
$MessagingConfig = New-TeamsRequestV2 -TenantFilter $TenantFilter -Type 'TeamsMessagingConfiguration' -Action Get -Identity 'Global'
29+
30+
if ($MessagingConfig) {
31+
$Data = @($MessagingConfig)
32+
Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsMessagingConfiguration' -Data $Data -AddCount
33+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Teams Messaging Configuration' -sev Debug
34+
}
35+
$MessagingConfig = $null
36+
37+
} catch {
38+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache Teams Messaging Configuration: $($_.Exception.Message)" -sev Error
39+
}
40+
}

Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMessagingPolicy.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function Set-CIPPDBCacheCsTeamsMessagingPolicy {
2525
try {
2626
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Teams Messaging Policy' -sev Debug
2727

28-
$MessagingPolicy = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Get-CsTeamsMessagingPolicy' -CmdParams @{ Identity = 'Global' }
28+
$MessagingPolicy = New-TeamsRequestV2 -TenantFilter $TenantFilter -Type 'TeamsMessagingPolicy' -Action Get -Identity 'Global'
2929

3030
if ($MessagingPolicy) {
3131
$Data = @($MessagingPolicy)

Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTenantFederationConfiguration.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function Set-CIPPDBCacheCsTenantFederationConfiguration {
2525
try {
2626
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Teams Tenant Federation Configuration' -sev Debug
2727

28-
$Federation = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Get-CsTenantFederationConfiguration' -CmdParams @{ Identity = 'Global' }
28+
$Federation = New-TeamsRequestV2 -TenantFilter $TenantFilter -Type 'TenantFederationConfiguration' -Action Get -Identity 'Global'
2929

3030
if ($Federation) {
3131
$Data = @($Federation)

0 commit comments

Comments
 (0)