mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 19:05:38 +02:00
314 lines
12 KiB
PowerShell
314 lines
12 KiB
PowerShell
function Get-LanguageString
|
|
{
|
|
param($String, $DefaultValue = $null, [switch]$IgnoreMissing)
|
|
|
|
$lng = ?? $script:CurrentLanguage "en"
|
|
|
|
$languageStrings = Get-CacheObject "LanguageStrings_$lng"
|
|
|
|
if(-not $languageStrings)
|
|
{
|
|
try {
|
|
# ReadAllText, not Get-Content: it defaults to UTF8 with BOM detection on
|
|
# both PS5.1 and PS7, so there is no -Encoding flag to forget. Bare
|
|
# Get-Content reads these as the ANSI codepage on 5.1 and mangles every
|
|
# non-Latin string. It is also ~3x faster on these 1-2 MB files.
|
|
$languageStrings = [IO.File]::ReadAllText(([IO.Path]::Combine($script:AppRootFolder, "Config", "LanguageStrings", "Strings-$($lng).json"))) | ConvertFrom-Json
|
|
Set-CacheObject "LanguageStrings_$lng" $languageStrings
|
|
}
|
|
catch {
|
|
Write-LogError "Failed to load language string for $lng" $_.Exception
|
|
}
|
|
}
|
|
|
|
if(!$String) { return }
|
|
|
|
$arrParts = $String.Split('.')
|
|
|
|
if([String]::IsNullOrEmpty($arrParts[-1])) { return }
|
|
|
|
$languageStringObject = $languageStrings
|
|
foreach($part in $arrParts.Split('.'))
|
|
{
|
|
if($languageStringObject.$part -or $part -eq "Empty")
|
|
{
|
|
$languageStringObject = $languageStringObject.$part
|
|
}
|
|
else
|
|
{
|
|
if($part -and $IgnoreMissing -ne $true)
|
|
{
|
|
# A segment starting with an uppercase letter is expected noise, not
|
|
# a defect. The language pipeline forbids sibling keys that differ
|
|
# only by case and resolves the clash by deleting the uppercase-
|
|
# leading one, subtree included. The lookup above is case-insensitive,
|
|
# so any uppercase key whose lowercase twin survived has already
|
|
# matched - what reaches here is a key Microsoft declares in setting
|
|
# metadata but never publishes in a resource file. Nothing we or the
|
|
# language project can fix, so keep it out of the normal log.
|
|
#
|
|
# -cmatch, not -match: PowerShell's default comparison is
|
|
# case-INsensitive, so -match '^[A-Z]' would silence every miss.
|
|
if($part -cmatch '^[A-Z]')
|
|
{
|
|
Write-LogDebug "Could not find string $String. Part '$part' was not found"
|
|
}
|
|
else
|
|
{
|
|
Write-Log "Could not find string $String. Part '$part' was not found"
|
|
}
|
|
}
|
|
return $DefaultValue
|
|
}
|
|
}
|
|
|
|
# A key can land on a CONTAINER rather than a leaf string - the portal ships
|
|
# both 'ScheduledAction.notification' (the action label) and a
|
|
# 'ScheduledAction.Notification' object of email-picker sub-strings, and the
|
|
# lookup above is case-insensitive. Calling .Trim() on that object threw and
|
|
# took the whole policy's documentation down with it. Callers that build a
|
|
# key from Graph data (an enum member, an @odata.type) cannot know in advance
|
|
# which they will hit, so treat a container as "not found".
|
|
if($languageStringObject -isnot [String])
|
|
{
|
|
if($IgnoreMissing -ne $true)
|
|
{
|
|
Write-Log "String $String resolves to a container, not a value. Using the default" 2
|
|
}
|
|
return $DefaultValue
|
|
}
|
|
|
|
return $languageStringObject.Trim("`n")
|
|
}
|
|
|
|
# Renamed from Get-TranslationFiles
|
|
function Get-PolicyObjectCategoryInfo
|
|
{
|
|
param($ODataType)
|
|
|
|
$policyObjectCategories = Get-CacheObject "PolicyObjectCategories"
|
|
|
|
if(-not $policyObjectCategories)
|
|
{
|
|
$policyObjectCategories = [IO.File]::ReadAllText(([IO.Path]::Combine($script:AppRootFolder, "Config", "ObjectCategories.json"))) | ConvertFrom-Json
|
|
Set-CacheObject "PolicyObjectCategories" $policyObjectCategories
|
|
}
|
|
|
|
return $policyObjectCategories | Where-Object ObjectType -eq $ODataType
|
|
}
|
|
|
|
function Get-PolicyObjectCategoryString
|
|
{
|
|
param($CategoryId)
|
|
|
|
if($CategoryId -is [String])
|
|
{
|
|
return Get-LanguageString (?: $CategoryId.Contains(".") $CategoryId "Category.$($CategoryId)")
|
|
}
|
|
|
|
$policyObjectCategoryIds = Get-CacheObject "PolicyObjectCategoryIds"
|
|
|
|
if(-not $policyObjectCategoryIds)
|
|
{
|
|
$policyObjectCategoryIds = [IO.File]::ReadAllText(([IO.Path]::Combine($script:AppRootFolder, "Config", "CategoryId.json"))) | ConvertFrom-Json
|
|
# Was never cached, so the file was re-read on every category lookup - once
|
|
# per documented row. The other three Config readers here all cache.
|
|
Set-CacheObject "PolicyObjectCategoryIds" $policyObjectCategoryIds
|
|
}
|
|
|
|
Get-LanguageString "Category.$($policyObjectCategoryIds."$($CategoryId)")"
|
|
}
|
|
|
|
function Get-ApplicationType
|
|
{
|
|
param($PolicyObject)
|
|
|
|
$appTypes = Get-CacheObject "ApplicationTypes"
|
|
|
|
if(-not $appTypes)
|
|
{
|
|
$fi = [IO.FileInfo]([IO.Path]::Combine($script:AppRootFolder, "Config", "AppTypes.json"))
|
|
if(!$fi.Exists)
|
|
{
|
|
return $false
|
|
}
|
|
$appTypes = [IO.File]::ReadAllText($fi.FullName) | ConvertFrom-Json
|
|
Set-CacheObject "ApplicationTypes" $appTypes
|
|
}
|
|
|
|
foreach($appType in ($appTypes | Where-Object ODataType -eq $PolicyObject.JsonObject.'@OData.Type'))
|
|
{
|
|
if($appType.Condition)
|
|
{
|
|
if($PolicyObject.JsonObject."$($appType.Condition.Property)" -eq $appType.Condition.Value)
|
|
{
|
|
return $appType
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return $appType
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-TemplatePolicyTypeName
|
|
{
|
|
param($ODataType, $Default = $null)
|
|
|
|
$categoryObject = Get-PolicyObjectCategoryInfo $ODataType
|
|
|
|
if($null -eq $categoryObject) { return $Default }
|
|
|
|
$policyName = Get-LanguageString "PolicyType.$($categoryObject.PolicyTypeLanguageId)"
|
|
|
|
if($policyName) { return $policyName }
|
|
|
|
return $Default
|
|
}
|
|
|
|
# Graph enum members and the portal's language ids do not always agree for the
|
|
# same platform. Where they differ, the portal ships the string under ITS id and
|
|
# will never publish one under the Graph name, so the mapping belongs here rather
|
|
# than in a request to the IntuneLanuageAndObjects pipeline.
|
|
#
|
|
# aosp deviceManagementConfigurationPlatforms member (Settings Catalog).
|
|
# The portal files it as androidAOSP - "Android (AOSP)" - and uses the
|
|
# same wording elsewhere (PolicyType.aospDeviceOwnerCompliancePolicy is
|
|
# "Android (AOSP) compliance policy").
|
|
#
|
|
# Only add an entry when the target key genuinely describes the SAME platform.
|
|
# Members with no equivalent string at all (windowsPhone81, the
|
|
# *MobileApplicationManagement pseudo-platforms, none / unknownFutureValue) are
|
|
# left to render blank and are reported by Tools/Audit-PolicyPlatforms.ps1.
|
|
$script:PlatformLanguageIdAliases = @{
|
|
'aosp' = 'androidAOSP'
|
|
}
|
|
|
|
# Platform label for an @odata.type that Config/ObjectCategories.json has no row
|
|
# for. Consulted ONLY as a fallback, so a row shipped by the language pipeline
|
|
# later automatically wins and this table quietly becomes redundant.
|
|
#
|
|
# The app / app-protection entries use the generic AppResources.AppTypePlatform
|
|
# namespace rather than Platform.*, on purpose: Platform.* renders enrollment
|
|
# flavours ("Android device administrator", "Android Enterprise"), which is the
|
|
# wording the portal uses on the CREATE blade. When LISTING these policies the
|
|
# portal says plain "Android" - and a list is what this feeds.
|
|
$script:PolicyPlatformOverrides = @{
|
|
# Apps. Get-GraphApplicationPlatform matches a platform token inside the
|
|
# @odata.type; these two carry none.
|
|
'#microsoft.graph.officeSuiteApp' = 'AppResources.AppTypePlatform.windows'
|
|
'#microsoft.graph.microsoftStoreForBusinessApp' = 'AppResources.AppTypePlatform.windows'
|
|
|
|
# App configuration for managed devices. androidManagedStoreAppConfiguration
|
|
# is deliberately absent - it HAS an ObjectCategories row and already
|
|
# resolves (as "Android Enterprise", from the generated data).
|
|
'#microsoft.graph.iosMobileAppConfiguration' = 'AppResources.AppTypePlatform.ios'
|
|
'#microsoft.graph.androidForWorkMobileAppConfiguration' = 'AppResources.AppTypePlatform.android'
|
|
|
|
# App protection. managedAppPolicies is polymorphic - one PolicyType serving
|
|
# several platforms - so this cannot be expressed as a type-level default.
|
|
'#microsoft.graph.androidManagedAppProtection' = 'AppResources.AppTypePlatform.android'
|
|
'#microsoft.graph.iosManagedAppProtection' = 'AppProtection.iOSPlatformLabel'
|
|
'#microsoft.graph.windowsManagedAppProtection' = 'AppResources.AppTypePlatform.windows'
|
|
'#microsoft.graph.mdmWindowsInformationProtectionPolicy' = 'AppResources.AppTypePlatform.windows'
|
|
|
|
# MAM app configuration targets iOS and Android apps from one policy, so no
|
|
# single platform applies.
|
|
'#microsoft.graph.targetedManagedAppConfiguration' = 'Platform.multiplePlatforms'
|
|
}
|
|
|
|
# Keys the portal does not publish. The Platform column is UI, and this project
|
|
# keeps UI text in English (documentation is what gets localized), so an English
|
|
# literal is the convention - same shape as the iOS/iPadOS fallback in
|
|
# IntuneApplicationClasses. If the pipeline ever ships the key, it wins.
|
|
$script:PlatformStringFallbacks = @{
|
|
'Platform.multiplePlatforms' = 'Multiple platforms'
|
|
}
|
|
|
|
function Get-PlatformStringByKey
|
|
{
|
|
param([string]$Key)
|
|
|
|
if([String]::IsNullOrWhiteSpace($Key)) { return $null }
|
|
|
|
$name = Get-LanguageString $Key -IgnoreMissing
|
|
if($name) { return $name }
|
|
|
|
if($script:PlatformStringFallbacks.ContainsKey($Key)) { return $script:PlatformStringFallbacks[$Key] }
|
|
|
|
return $null
|
|
}
|
|
|
|
# The override for one @odata.type, or $null when there is none.
|
|
function Get-PolicyPlatformOverride
|
|
{
|
|
param([string]$ODataType)
|
|
|
|
if([String]::IsNullOrWhiteSpace($ODataType)) { return $null }
|
|
if(-not $script:PolicyPlatformOverrides.ContainsKey($ODataType)) { return $null }
|
|
|
|
return Get-PlatformStringByKey $script:PolicyPlatformOverrides[$ODataType]
|
|
}
|
|
|
|
# Translate a platform enum value returned by Graph.
|
|
#
|
|
# Several endpoints return a comma-separated flags value rather than a single
|
|
# member - configurationPolicies hands back "android,iOS" for a policy that
|
|
# targets both - and there is no language key for the combined string, so the
|
|
# naive lookup returned nothing and the Platform column rendered blank.
|
|
# Translate each member and join.
|
|
#
|
|
# Returns $null unless EVERY member translates: a partially resolved value would
|
|
# render as a misleadingly narrow platform (showing just "iOS/iPadOS" for an
|
|
# android+iOS policy), and a blank is honest. Tools/Audit-PolicyPlatforms.ps1
|
|
# reports what is still blank and why.
|
|
function Get-PlatformDisplayName
|
|
{
|
|
param([string]$Value)
|
|
|
|
if([String]::IsNullOrWhiteSpace($Value)) { return $null }
|
|
|
|
$parts = @($Value.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
|
if($parts.Count -eq 0) { return $null }
|
|
|
|
$names = @()
|
|
foreach($part in $parts)
|
|
{
|
|
# Hashtable lookup is case-insensitive, which is what we want - the same
|
|
# platform appears as both 'aosp' and 'AOSP' across Graph payloads.
|
|
$key = if($script:PlatformLanguageIdAliases.ContainsKey($part)) { $script:PlatformLanguageIdAliases[$part] } else { $part }
|
|
|
|
$name = Get-LanguageString "Platform.$key" -IgnoreMissing
|
|
if(-not $name) { return $null }
|
|
$names += $name
|
|
}
|
|
|
|
return ($names -join ", ")
|
|
}
|
|
|
|
function Get-PolicyPlatformName
|
|
{
|
|
param($ODataType, $Default = $null)
|
|
|
|
$categoryObject = Get-PolicyObjectCategoryInfo $ODataType
|
|
|
|
if($null -eq $categoryObject)
|
|
{
|
|
# No generated row - fall back to the hand-maintained overrides.
|
|
$override = Get-PolicyPlatformOverride $ODataType
|
|
if($override) { return $override }
|
|
|
|
return $Default
|
|
}
|
|
|
|
$platformName = Get-LanguageString "Platform.$($categoryObject.PlatformLanguageId)"
|
|
|
|
if($platformName) { return $platformName }
|
|
|
|
# Row exists but its PlatformLanguageId does not translate.
|
|
$override = Get-PolicyPlatformOverride $ODataType
|
|
if($override) { return $override }
|
|
|
|
return $Default
|
|
} |