mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 10:55:38 +02:00
456 lines
17 KiB
PowerShell
456 lines
17 KiB
PowerShell
#ImportOrder 220
|
|
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("TypeNotFound","", Justification = "")]
|
|
class AppConfigurationGroup : IntunePolicyGroupBase
|
|
{
|
|
AppConfigurationGroup() : Base()
|
|
{
|
|
$this.Init()
|
|
}
|
|
|
|
Init()
|
|
{
|
|
$this._ID = "AppConfiguration"
|
|
$this._Name = "App configuration policies"
|
|
$this._Icon = "AppConfiguration"
|
|
$this._ExtraColumns = @("EnrolmentType=Enrolment type")
|
|
}
|
|
}
|
|
|
|
#########################################################################################
|
|
#
|
|
# App Configuration (App)
|
|
#
|
|
#########################################################################################
|
|
|
|
# region App Configuration (App)
|
|
class AppConfigurationManagedAppType : IntunePolicyTypeBase
|
|
{
|
|
AppConfigurationManagedAppType() : Base()
|
|
{
|
|
$this.Init()
|
|
}
|
|
|
|
Init()
|
|
{
|
|
$this._PolicyGroup = (Get-SingletonObject "AppConfigurationGroup")
|
|
$this._PolicyName = "App configuration (App)"
|
|
$this._ID = "AppConfigurationManagedApp"
|
|
# Endpoint rejects a name $filter (verified live 2026-08-27) - searches filter client-side.
|
|
$this._SupportsNameFilter = $false
|
|
$this._API = "deviceAppManagement/targetedManagedAppConfigurations"
|
|
$this._Permissions = @("DeviceManagementApps.ReadWrite.All")
|
|
$this._Dependencies = @("Applications")
|
|
$this._ObjectClass = "AppConfigurationManagedAppObject"
|
|
$this._ExpandAssignmentsList = $false
|
|
# CheckPolicy authoritatively matches this type by @odata.type (plus the base
|
|
# @odata.id fallback), so a rejection is real - skip the folder-trust fallback.
|
|
$this._StrictODataTypeCheck = $true
|
|
$this._Icon = "AppConfiguration"
|
|
|
|
if($null -ne $this._PolicyGroup) {
|
|
$this._PolicyGroup.AddPolicyType($this)
|
|
}
|
|
}
|
|
|
|
[Boolean]CheckPolicy([PSCustomObject]$PolicyObject)
|
|
{
|
|
# targetedManagedAppConfiguration is surfaced by the polymorphic
|
|
# managedAppPolicies collection alongside App Protection variants, so match it
|
|
# explicitly by @odata.type. This also lets a file object (which carries only
|
|
# @odata.type, no top-level @odata.id) resolve to this type; the base
|
|
# @odata.id-based CheckPolicy would reject it.
|
|
if($PolicyObject.'@odata.type' -eq '#microsoft.graph.targetedManagedAppConfiguration')
|
|
{
|
|
return $true
|
|
}
|
|
|
|
# Fall back to the base @odata.id matcher (deviceAppManagement/targetedManagedAppConfigurations)
|
|
# for objects that carry an id but no top-level @odata.type.
|
|
return ([IntunePolicyTypeBase]$this).CheckPolicy($PolicyObject)
|
|
}
|
|
|
|
[Hashtable]PreImportCommand([PSCustomObject]$PolicyObject)
|
|
{
|
|
# apps is a navigation property set after create via targetApps
|
|
# (PostImportCommand); strip it, then POST to the type API
|
|
# (deviceAppManagement/targetedManagedAppConfigurations).
|
|
Remove-Property $PolicyObject.JsonObject "apps"
|
|
Remove-Property $PolicyObject.JsonObject "apps@odata.context"
|
|
|
|
return $null
|
|
}
|
|
|
|
PostImportCommand([PSCustomObject]$PolicyObject, [PSCustomObject]$SourceObject)
|
|
{
|
|
if($SourceObject.Object.Apps) {
|
|
# No "@odata.type" on the created object so reload new object
|
|
#$newObject = (Invoke-MSGraphAPI "$($objectType.API)?`$filter=id eq '$($obj.Id)'").Value
|
|
$newObject = Invoke-MSGraphAPI -Url "$($this.API)/$($PolicyObject.Id)" -TokenId $PolicyObject._TokenID
|
|
if($newObject)
|
|
{
|
|
try
|
|
{
|
|
$apps = [PSCustomObject]@{
|
|
appGroupType = $PolicyObject.Object.appGroupType
|
|
apps = @($SourceObject.Object.Apps)
|
|
}
|
|
$json = $apps | ConvertTo-Json -Depth 20
|
|
|
|
Invoke-MSGraphAPI -Url "$($this.API)/$($PolicyObject.Id)/targetApps" -Content $json -HttpMethod POST -TokenId $PolicyObject._TokenID | Out-Null
|
|
}
|
|
catch {
|
|
# The policy was created; only the targetApps association failed. Keep
|
|
# going but make the partial import visible instead of swallowing it.
|
|
Write-LogError "Failed to assign target apps to imported App configuration policy '$($PolicyObject.displayName)' ($($PolicyObject.Id)) via $($this.API)/$($PolicyObject.Id)/targetApps" $_.Exception
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Hashtable]PreUpdateCommand([PSCustomObject]$PolicyObject, [PSCustomObject]$ExistingObject)
|
|
{
|
|
# apps is a navigation property set via targetApps, not inline. Re-post
|
|
# apps to the type API, strip them from the PATCH body, then PATCH the
|
|
# type API (deviceAppManagement/targetedManagedAppConfigurations).
|
|
if($PolicyObject.JsonObject.apps)
|
|
{
|
|
try
|
|
{
|
|
$apps = [PSCustomObject]@{
|
|
appGroupType = $PolicyObject.JsonObject.appGroupType
|
|
apps = @($PolicyObject.JsonObject.apps)
|
|
}
|
|
$json = $apps | ConvertTo-Json -Depth 20
|
|
Invoke-MSGraphAPI -Url "$($this.API)/$($ExistingObject.Id)/targetApps" -Content $json -HttpMethod POST -TokenId $ExistingObject._TokenID | Out-Null
|
|
}
|
|
catch {
|
|
# The policy PATCH still proceeds; surface the failed targetApps update.
|
|
Write-LogError "Failed to update target apps for App configuration policy '$($ExistingObject.displayName)' ($($ExistingObject.Id)) via $($this.API)/$($ExistingObject.Id)/targetApps" $_.Exception
|
|
}
|
|
}
|
|
Remove-Property $PolicyObject.JsonObject "apps"
|
|
Remove-Property $PolicyObject.JsonObject "apps@odata.context"
|
|
# assignments is a navigation property (managed via the /assign action),
|
|
# not inline-PATCHable.
|
|
Remove-Property $PolicyObject.JsonObject "assignments"
|
|
Remove-Property $PolicyObject.JsonObject "assignments@odata.context"
|
|
|
|
return $null
|
|
}
|
|
}
|
|
|
|
Class AppConfigurationManagedAppObject : IntunePolicyBase
|
|
{
|
|
Hidden [string]$_objectClass = $null
|
|
|
|
AppConfigurationManagedAppObject([PSCustomObject]$JsonObj) : Base($JsonObj) { $this.Init() }
|
|
|
|
AppConfigurationManagedAppObject() : Base()
|
|
{
|
|
$this.Init()
|
|
}
|
|
|
|
Hidden Init()
|
|
{
|
|
$this._PolicyType = (Get-SingletonObject "AppConfigurationManagedAppType")
|
|
|
|
Add-ObjectProperty $this "EnrolmentType" { "Managed apps" }
|
|
|
|
Get-AppConfigurationClass $this
|
|
|
|
# Targeted-app resolution runs through the sub-resource contract; see the
|
|
# Get-/Add-/Build-AppConfigTargetApp* helpers below.
|
|
$this._HasSubResourceBatch = $true
|
|
}
|
|
|
|
[PSCustomObject[]] GetSubResourceBatchRequests([int]$Phase)
|
|
{
|
|
if($Phase -ne 1) { return @() }
|
|
return (Get-AppConfigTargetAppRequests $this)
|
|
}
|
|
|
|
[PSCustomObject[]] ApplySubResourceBatchResult([int]$Phase, [string]$Key, $Body)
|
|
{
|
|
if($Phase -eq 1) { Add-AppConfigTargetAppResult $Key $Body }
|
|
return @()
|
|
}
|
|
|
|
[void] FinalizeSubResources()
|
|
{
|
|
Build-AppConfigTargetAppRefs $this
|
|
}
|
|
}
|
|
|
|
#########################################################################################
|
|
#
|
|
# App Configuration (Device)
|
|
#
|
|
#########################################################################################
|
|
|
|
# region App Configuration (Device)
|
|
class AppConfigurationManagedDeviceType : IntunePolicyTypeBase
|
|
{
|
|
AppConfigurationManagedDeviceType() : Base()
|
|
{
|
|
$this.Init()
|
|
}
|
|
|
|
Init()
|
|
{
|
|
$this._PolicyGroup = (Get-SingletonObject "AppConfigurationGroup")
|
|
$this._PolicyName = "App configuration (Device)"
|
|
$this._ID = "AppConfigurationManagedDevice"
|
|
$this._API = "deviceAppManagement/mobileAppConfigurations"
|
|
$this._QueryList = "?`$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig%20eq%20false%20or%20isof(%27microsoft.graph.androidManagedStoreAppConfiguration%27)%20eq%20false"
|
|
$this._Permissions = @("DeviceManagementApps.ReadWrite.All")
|
|
$this._Dependencies = @("Applications")
|
|
$this._ObjectClass = "AppConfigurationManagedDeviceObject"
|
|
$this._ExpandAssignmentsList = $false
|
|
$this._Icon = "AppConfiguration"
|
|
|
|
if($null -ne $this._PolicyGroup) {
|
|
$this._PolicyGroup.AddPolicyType($this)
|
|
}
|
|
}
|
|
|
|
[Hashtable]PreImportAssignmentsCommand([PSCustomObject]$PolicyObject, [PSCustomObject]$SourceObject)
|
|
{
|
|
return (@{"API"="deviceAppManagement/mobileAppConfigurations/$($PolicyObject.Id)/microsoft.graph.managedDeviceMobileAppConfiguration/assign"})
|
|
}
|
|
|
|
[Hashtable]PreImportCommand([PSCustomObject]$PolicyObject)
|
|
{
|
|
Import-AppConfigurationTargetedApps $PolicyObject
|
|
return $null
|
|
}
|
|
}
|
|
|
|
Class AppConfigurationManagedDeviceObject : IntunePolicyBase
|
|
{
|
|
AppConfigurationManagedDeviceObject([PSCustomObject]$JsonObj) : Base($JsonObj) { $this.Init() }
|
|
|
|
AppConfigurationManagedDeviceObject() : Base()
|
|
{
|
|
$this.Init()
|
|
}
|
|
|
|
Hidden Init()
|
|
{
|
|
$this._PolicyType = (Get-SingletonObject "AppConfigurationManagedDeviceType")
|
|
|
|
Add-ObjectProperty $this "EnrolmentType" { "Managed devices" }
|
|
|
|
# Targeted-app resolution runs through the sub-resource contract; see the
|
|
# Get-/Add-/Build-AppConfigTargetApp* helpers.
|
|
$this._HasSubResourceBatch = $true
|
|
}
|
|
|
|
[PSCustomObject[]] GetSubResourceBatchRequests([int]$Phase)
|
|
{
|
|
if($Phase -ne 1) { return @() }
|
|
return (Get-AppConfigTargetAppRequests $this)
|
|
}
|
|
|
|
[PSCustomObject[]] ApplySubResourceBatchResult([int]$Phase, [string]$Key, $Body)
|
|
{
|
|
if($Phase -eq 1) { Add-AppConfigTargetAppResult $Key $Body }
|
|
return @()
|
|
}
|
|
|
|
[void] FinalizeSubResources()
|
|
{
|
|
Build-AppConfigTargetAppRefs $this
|
|
}
|
|
}
|
|
|
|
#########################################################################################
|
|
#
|
|
# Generic Functions
|
|
#
|
|
#########################################################################################
|
|
|
|
function Get-AppConfigurationClass
|
|
{
|
|
param($Policy)
|
|
|
|
try {
|
|
$tmp = $Policy.Object."@odata.type".Split('.')[-1]
|
|
$Policy._objectClass = Get-GraphObjectClassName $tmp
|
|
}
|
|
catch { }
|
|
|
|
if($null -eq $Policy._objectClass) {
|
|
Write-Log "Could not get class name for $($Policy.Name) ($($Policy.Object."@odata.type"))" 3
|
|
}
|
|
}
|
|
|
|
function Get-AppConfigurationFullObject
|
|
{
|
|
param($Policy)
|
|
|
|
if(-not $Policy.Object."@odata.type" -or -not $Policy._objectClass) { return $false }
|
|
|
|
$expand = $null
|
|
if($Policy._objectClass -eq "windowsInformationProtectionPolicies")
|
|
{
|
|
$expand = "?`$expand=protectedAppLockerFiles,exemptAppLockerFiles"
|
|
}
|
|
else {
|
|
$url = $Policy.GetObjectURL()
|
|
|
|
$tmpArr = $url.Split("?")
|
|
if($tmpArr.Length -gt 1) {
|
|
$expand = "?" + $tmpArr[1]
|
|
}
|
|
}
|
|
|
|
$fullObject = (Invoke-MSGraphAPI -Url "deviceAppManagement/$($Policy._objectClass)/$($Policy.Id)$expand" -TokenId $Policy._TokenId)
|
|
if($fullObject)
|
|
{
|
|
$Policy.JsonObject = $fullObject
|
|
$Policy._IsFullObject = $true
|
|
|
|
return $true
|
|
}
|
|
return $false
|
|
}
|
|
|
|
# Targeted-app resolution for AppConfiguration policies.
|
|
#
|
|
# The policy body lists app IDs in targetedMobileApps; cross-tenant export needs
|
|
# each app's displayName + @odata.type to re-map them on import into another
|
|
# tenant (#CustomRefTargetedApps). The mobileApps/<id> lookup is owned ONLY by
|
|
# Get-AppConfigTargetAppRequests below — the sub-resource contract on the
|
|
# AppConfiguration*Object classes drives the fetch (coalesced across policies by
|
|
# Invoke-PolicySubresourceFetch's URL de-dup), caches results, then builds the
|
|
# ref string. Previously this was duplicated in Sync-BulkExportAppConfigurationTargetApps
|
|
# (Internal/PolicyHydrateExtras.ps1).
|
|
|
|
# Process-wide cache: appId -> app body (or $null for a 404/miss). Shared across
|
|
# every AppConfig policy in a hydrate run so the same app is fetched once.
|
|
function Get-AppConfigTargetAppCache
|
|
{
|
|
if($null -eq $script:_appConfigTargetAppCache) { $script:_appConfigTargetAppCache = @{} }
|
|
return $script:_appConfigTargetAppCache
|
|
}
|
|
|
|
# Only these polymorphic AppConfig @odata.types carry targetedMobileApps that
|
|
# need tenant-specific remapping. (androidManagedAppProtection is listed for
|
|
# parity with the legacy filter; App Protection policies use `apps`, not
|
|
# `targetedMobileApps`, so they never actually match.)
|
|
function Test-AppConfigHasTargetApps
|
|
{
|
|
param($Policy)
|
|
return ($Policy.JsonObject.'@OData.Type' -in @(
|
|
'#microsoft.graph.androidManagedAppProtection',
|
|
'#microsoft.graph.androidForWorkMobileAppConfiguration',
|
|
'#microsoft.graph.androidManagedStoreAppConfiguration',
|
|
'#microsoft.graph.iosMobileAppConfiguration'
|
|
) -and @($Policy.JsonObject.targetedMobileApps).Count -gt 0)
|
|
}
|
|
|
|
# Sub-resource requests for every targeted app not already cached. The
|
|
# deviceAppManagement/mobileApps/<id> URL lives ONLY here.
|
|
function Get-AppConfigTargetAppRequests
|
|
{
|
|
param($Policy)
|
|
if(-not (Test-AppConfigHasTargetApps $Policy)) { return @() }
|
|
$cache = Get-AppConfigTargetAppCache
|
|
$reqs = [System.Collections.Generic.List[PSCustomObject]]::new()
|
|
foreach($appId in @($Policy.JsonObject.targetedMobileApps)) {
|
|
if(-not $appId -or $cache.ContainsKey($appId)) { continue }
|
|
[void]$reqs.Add([PSCustomObject]@{
|
|
Key = "targetApp_$appId"
|
|
Url = "deviceAppManagement/mobileApps/$appId"
|
|
Headers = @{ Accept = 'application/json;odata.metadata=minimal' }
|
|
})
|
|
}
|
|
return $reqs.ToArray()
|
|
}
|
|
|
|
# Cache one targeted-app sub-resource response. Stores $null for misses so 404s
|
|
# aren't re-requested by a later policy in the same run.
|
|
function Add-AppConfigTargetAppResult
|
|
{
|
|
param([string]$Key, $Body)
|
|
if($Key -notlike 'targetApp_*') { return }
|
|
$appId = $Key.Substring('targetApp_'.Length)
|
|
(Get-AppConfigTargetAppCache)[$appId] = $Body
|
|
}
|
|
|
|
# Build #CustomRefTargetedApps from the cached app bodies (finalize step).
|
|
function Build-AppConfigTargetAppRefs
|
|
{
|
|
param($Policy)
|
|
if(-not (Test-AppConfigHasTargetApps $Policy)) { return }
|
|
$cache = Get-AppConfigTargetAppCache
|
|
$targetedApps = @()
|
|
foreach($appId in @($Policy.JsonObject.targetedMobileApps)) {
|
|
$appObj = $cache[$appId]
|
|
if($appObj) {
|
|
Write-Log "Add target app info $($appObj.displayName) ($($appObj.Id)) of type $($appObj.'@OData.Type')"
|
|
$targetedApps += $appObj.displayName + '|!|' + $appObj.Id + '|!|' + $appObj.'@OData.Type'
|
|
}
|
|
else {
|
|
Write-Log "No app found with id $appId" 2
|
|
}
|
|
}
|
|
if($targetedApps.Count -gt 0) {
|
|
Add-Member -InputObject $Policy.JsonObject -MemberType NoteProperty -Name '#CustomRefTargetedApps' -Value ($targetedApps -join '|*|') -Force
|
|
}
|
|
}
|
|
|
|
function Import-AppConfigurationTargetedApps
|
|
{
|
|
param($Policy)
|
|
|
|
if($Policy.JsonObject."#CustomRefTargetedApps" -and $Policy.JsonObject.targetedMobileApps)
|
|
{
|
|
Write-Log "Adding app targets for $($Policy.JsonObject.displayName)"
|
|
|
|
$targetedAppsInfo = $Policy.JsonObject."#CustomRefTargetedApps"
|
|
|
|
$translatedTargetedApps = @()
|
|
|
|
if($targetedAppsInfo)
|
|
{
|
|
foreach($targetedApp in ($targetedAppsInfo -split "[|][*][|]"))
|
|
{
|
|
$appName, $appId, $appType = $targetedApp -split "[|][!][|]"
|
|
if(-not $appName -or -not $appId)
|
|
{
|
|
Write-Log "App Name and Id is missing in string: $appApp" 2
|
|
continue
|
|
}
|
|
$tmpApps = (Invoke-MSGraphAPI -Url "/deviceAppManagement/mobileApps?`$filter=displayName eq '$appName'" -TokenId $Policy._TokenId).value
|
|
if(-not $tmpApps)
|
|
{
|
|
Write-Log "No application found with name $appName. $appId will not be translated and added to target list" 2
|
|
continue
|
|
}
|
|
$tmpApp = $tmpApps | Where-Object '@OData.Type' -eq $appType
|
|
if(-not $tmpApp)
|
|
{
|
|
Write-Log "No $appName application found of type $appType. $appId will not be translated and added to target list" 2
|
|
}
|
|
elseif(($tmpApp | Measure-Object).Count -gt 1) {
|
|
Write-Log "$(($tmpApp | Measure-Object).Count) applications found with name '$appName' of type $appType. $appId will not be translated and added to target list" 2
|
|
}
|
|
else {
|
|
Write-Log "Found '$appName' with id $($tmpApp.Id) ($appType)"
|
|
$translatedTargetedApps += $tmpApp.Id
|
|
}
|
|
}
|
|
|
|
if($translatedTargetedApps.Count -gt 0) {
|
|
Write-Log "Updating translated targeted apps"
|
|
$Policy.JsonObject.targetedMobileApps = $translatedTargetedApps
|
|
}
|
|
else {
|
|
Write-Log "Could not find targeted apps in the evnironment. Verify that they are added. Policy import might fail" 3
|
|
}
|
|
}
|
|
}
|
|
}
|