Files
IntuneManagement/Classes/IntuneAppProtectionClasses.ps1

246 lines
11 KiB
PowerShell

#ImportOrder 220
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("TypeNotFound","", Justification = "")]
class AppProtectionGroup : IntunePolicyGroupBase
{
AppProtectionGroup() : Base()
{
$this.Init()
}
Init()
{
$this._ID = "AppProtection"
$this._Name = "App protection policies"
$this._Icon = "AppProtection"
}
}
#########################################################################################
#
# App Protection
#
#########################################################################################
# region App Protection
class AppProtectionType : IntunePolicyTypeBase
{
AppProtectionType() : Base()
{
$this.Init()
}
Init()
{
$this._PolicyGroup = (Get-SingletonObject "AppProtectionGroup")
$this._PolicyName = "App protection policy"
$this._ID = "AppProtection"
$this._SubTypeColumn = "ManagementType=Management type"
# Endpoint rejects a name $filter (verified live 2026-08-27) - searches filter client-side.
$this._SupportsNameFilter = $false
$this._API = "deviceAppManagement/managedAppPolicies"
$this._Permissions = @("DeviceManagementApps.ReadWrite.All")
$this._Dependencies = @("Applications")
$this._ObjectClass = "AppProtectionPolicyObject"
$this._ExpandAssignmentsList = $false
# Assignments are fetched per platform collection - see
# GetAssignmentsBaseURL below. _AssignmentsViaExpand stays $false:
# once the URL targets a concrete subtype the plain navigation GET
# works, and it returns just the assignments instead of the whole policy.
$this._PropertiesToRemove = @('exemptAppLockerFiles')
$this._PropertiesToRemoveForUpdate = @("protectedAppLockerFiles","version") # ToDo: !!! Add support for protectedAppLockerFiles?
$this._VerifyObject = $true
# CheckPolicy is a complete @odata.type matcher (the managedAppPolicies allowlist),
# so a rejection is authoritative - do not let the folder-trust fallback rescue a
# foreign object misplaced in this type's export folder.
$this._StrictODataTypeCheck = $true
$this._Icon = "AppConfiguration"
if($null -ne $this._PolicyGroup) {
$this._PolicyGroup.AddPolicyType($this)
}
}
# managedAppPolicies is Collection(managedAppPolicy), and managedAppPolicy
# declares no navigation properties - so both {API}/{id}/assignments and
# {API}/{id}?$expand=assignments return 400 ("Could not find a property named
# 'assignments' on type 'microsoft.graph.managedAppPolicy'"). Every concrete
# subtype inherits `assignments`, so route through the per-platform collection
# (_objectClass, set in the object's constructor via Get-AppConfigurationClass).
# defaultManagedAppProtection is the exception - it has no assignments at all.
[String]GetAssignmentsBaseURL([PSCustomObject]$PolicyObject)
{
if(-not $PolicyObject._objectClass) { return $this._API }
if($PolicyObject._objectClass -eq "defaultManagedAppProtections") { return $null }
return "deviceAppManagement/$($PolicyObject._objectClass)"
}
[Hashtable]PreImportCommand([PSCustomObject]$PolicyObject)
{
# apps is a navigation property set later via targetApps (PostImportCommand),
# not an inline body property - strip it from the POST body.
Remove-Property $PolicyObject.JsonObject "apps"
Remove-Property $PolicyObject.JsonObject "apps@odata.context"
# The polymorphic managedAppPolicies collection (used for listing) rejects
# POST, so import must target the per-platform collection. _objectClass is
# the metadata-derived endpoint segment (e.g. iosManagedAppProtections),
# set on the object at construction via Get-AppConfigurationClass.
if($PolicyObject._objectClass)
{
return @{"API"="deviceAppManagement/$($PolicyObject._objectClass)"}
}
return (@{})
}
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
# Created object carries no @odata.type; use the source object's
# metadata-derived endpoint segment (_objectClass).
if($SourceObject._objectClass)
{
Invoke-MSGraphAPI -Url "deviceAppManagement/$($SourceObject._objectClass)/$($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 protection policy '$($PolicyObject.displayName)' ($($PolicyObject.Id)) via deviceAppManagement/$($SourceObject._objectClass)/$($PolicyObject.Id)/targetApps" $_.Exception
}
}
}
}
[Hashtable]PreImportAssignmentsCommand([PSCustomObject]$PolicyObject, [PSCustomObject]$SourceObject)
{
# /assign is bound to the concrete platform subtype; the polymorphic
# managedAppPolicies collection returns 400 for the assign action.
if($SourceObject._objectClass)
{
return @{"API"="deviceAppManagement/$($SourceObject._objectClass)/$($PolicyObject.Id)/assign"}
}
return $null
}
[Hashtable]PreUpdateCommand([PSCustomObject]$PolicyObject, [PSCustomObject]$ExistingObject)
{
# managedAppPolicies rejects PATCH, and apps is a navigation property set
# via targetApps rather than inline. Re-post apps to the per-platform
# endpoint, strip them from the PATCH body, then PATCH that endpoint.
#
# Routing note: an imported object's POST response carries no
# @odata.type, so ITS _objectClass can be null - the update object came
# from a file that always has the type, so prefer that one.
if(-not $ExistingObject._objectClass -and $PolicyObject._objectClass)
{
$ExistingObject._objectClass = $PolicyObject._objectClass
}
if($PolicyObject.JsonObject.apps -and $ExistingObject._objectClass)
{
try
{
$apps = [PSCustomObject]@{
appGroupType = $PolicyObject.JsonObject.appGroupType
apps = @($PolicyObject.JsonObject.apps)
}
$json = $apps | ConvertTo-Json -Depth 20
Invoke-MSGraphAPI -Url "deviceAppManagement/$($ExistingObject._objectClass)/$($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 protection policy '$($ExistingObject.displayName)' ($($ExistingObject.Id)) via deviceAppManagement/$($ExistingObject._objectClass)/$($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 - PATCHing it 400s on the platform entity type.
Remove-Property $PolicyObject.JsonObject "assignments"
Remove-Property $PolicyObject.JsonObject "assignments@odata.context"
if($ExistingObject._objectClass)
{
return @{"API"="deviceAppManagement/$($ExistingObject._objectClass)/$($ExistingObject.Id)"}
}
return $null
}
[Boolean]CheckPolicy([PSCustomObject]$PolicyObject)
{
# App Protection owns the polymorphic deviceAppManagement/managedAppPolicies
# collection: the per-platform *ManagedAppProtection variants (ios / android /
# windows / default) plus the two Windows Information Protection policy types.
# An explicit allowlist is required: file objects carry only @odata.type (no
# top-level @odata.id), so this runs as the file->type discriminator. A previous
# "accept everything except targetedManagedAppConfiguration" greedily claimed
# unrelated policy types (Compliance, CA, etc.) when resolving from an export
# folder. targetedManagedAppConfiguration is App Config, not App Protection
# (see AppConfigurationManagedAppType.CheckPolicy).
$odata = [string]$PolicyObject.'@odata.type'
if($odata -match 'ManagedAppProtection$' -or
$odata -eq '#microsoft.graph.mdmWindowsInformationProtectionPolicy' -or
$odata -eq '#microsoft.graph.windowsInformationProtectionPolicy')
{
return $true
}
return $false
}
}
Class AppProtectionPolicyObject : IntunePolicyBase
{
Hidden [string]$_objectClass = $null
Hidden [string]$_managemntType = $null
AppProtectionPolicyObject([PSCustomObject]$JsonObj) : Base($JsonObj) { $this.Init() }
AppProtectionPolicyObject() : Base()
{
$this.Init()
}
Hidden Init()
{
$this._PolicyType = (Get-SingletonObject "AppProtectionType")
if($this.Object."@odata.type" -eq "#microsoft.graph.mdmWindowsInformationProtectionPolicy") {
$this._managemntType = "With enrollment"
}
elseif($this.Object."@odata.type" -eq "#microsoft.graph.windowsInformationProtectionPolicy") {
$this._managemntType = "Without enrollment"
}
else {
$this._managemntType = "All app types"
}
Add-ObjectProperty $this "ManagementType" { $this._managemntType }
Get-AppConfigurationClass $this
if($this.JsonObject."@odata.type" -eq "#microsoft.graph.iosManagedAppProtection") {
$platformName = Get-LanguageString "AppProtection.iOSPlatformLabel"
if($platformName) {
#$this._PlatformName = $platformName
}
}
}
}