mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 19:05:38 +02:00
522 lines
26 KiB
PowerShell
522 lines
26 KiB
PowerShell
# ObjectInfo JSON walker — the core of the generic Profile input provider.
|
|
#
|
|
# Ported from old Extensions/Documentation.psm1:2276 (Invoke-TranslateSection,
|
|
# ~440 LOC) plus the Invoke-VerifyCondition helper (~70 LOC) and
|
|
# Get-CultureLanguageString (~50 LOC).
|
|
#
|
|
# Drives schema-driven translation for ~140 policy types catalogued in
|
|
# Config/ObjectCategories.json. Each ObjectInfo JSON file under
|
|
# Config/ObjectInfo/<category>_<policyType>.json describes the per-property
|
|
# metadata (dataType, entityKey, nameResourceKey, child layout) and the
|
|
# walker dispatches each property to the appropriate translate primitive
|
|
# based on dataType.
|
|
|
|
# ---- Section walker ----
|
|
|
|
# Tracks the parent prop being walked so propLevel adjusts correctly when
|
|
# recursing into children. Module-scope (replaces old $script:currentParent).
|
|
$script:_currentSectionParent = $null
|
|
|
|
# Defaults: properties whose nameResourceKey shows up in this list are skipped
|
|
# entirely (purely visual elements in the old portal that don't translate
|
|
# to documentation content). Old code at Documentation.psm1:2977.
|
|
# ToDo: Review if these should be implemented or actually ignored
|
|
$script:_categoriesToIgnore = @(
|
|
'defenderSecurityCenterContactOptionsText'
|
|
'globalConfigurationsDescription','generalNetworkSettingsHeader'
|
|
'firewallCreateRules','exploitGuardCFHeadingText','exploitGuardNFTitle'
|
|
'exploitGuardEPExplainationPart1','exploitGuardEPExplainationPart2'
|
|
'exploitGuardEPExplainationPart3','exploitGuardEPExplainationPart4'
|
|
'defenderSecurityCenterSubHeaderText','defenderSecurityCenterITContactInformationSubHeaderText'
|
|
'windows10EndpointProtectionDeviceGuardLearnMore'
|
|
'win10DefaultPrivacyHeader','dfciBuiltinHeaderDescName'
|
|
)
|
|
|
|
# Resource keys that upstream renamed while the blade metadata kept referencing
|
|
# the old name. Microsoft's own portal cannot render these tooltips either, so
|
|
# there is nothing to wait for - map them to the current name.
|
|
# Confirmed 2026-08-22 by the IntuneLanuageAndObjects generator, which extracts
|
|
# the portal's ClientResources verbatim.
|
|
$script:_resourceKeyAliases = @{
|
|
'autoInstallAndRebootAtScheduledTime' = 'autoInstallAndRebootAtScheduledTimeOption'
|
|
# Only connecteddevices_iosgeneral.json references this, so the iOS variant
|
|
# is the correct target; a MacOS variant also exists upstream.
|
|
'blockAirPrintiBeaconDiscoveryDescription' = 'blockAirPrintiBeaconDiscoveryDescriptionIOS'
|
|
}
|
|
|
|
# Resolve an ObjectInfo resource key to its display string, or $null.
|
|
#
|
|
# Keys without a namespace live under SettingDetails. Two upstream quirks are
|
|
# handled here so callers do not each reimplement them:
|
|
#
|
|
# - Purely numeric keys are portal metadata artifacts, not string ids. The
|
|
# AndroidDeviceOwner and AOSP PKCS files carry emptyValueResourceKey:"1"
|
|
# verbatim from the blade metadata, which resolves to nothing and logs a
|
|
# "Could not find string" warning on every documented policy. Skipped the way
|
|
# the existing 'Empty' / 'LearnMore' sentinels are.
|
|
# - Renamed keys are redirected via $script:_resourceKeyAliases.
|
|
function Get-ObjectInfoResourceString {
|
|
param(
|
|
[string]$Key,
|
|
# emptyValueResourceKey values are already fully qualified upstream and
|
|
# must not get the SettingDetails prefix.
|
|
[switch]$NoPrefix
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Key)) { return $null }
|
|
if ($Key -match '^\d+$') { return $null }
|
|
|
|
if ($script:_resourceKeyAliases.ContainsKey($Key)) { $Key = $script:_resourceKeyAliases[$Key] }
|
|
|
|
$full = if ($NoPrefix -or $Key.Contains('.')) { $Key } else { "SettingDetails.$Key" }
|
|
try { return Get-LanguageString $full }
|
|
catch {
|
|
Write-Log "Get-LanguageString '$full' failed: $($_.Exception.Message)" 2
|
|
return $null
|
|
}
|
|
}
|
|
|
|
# Walk a flat settings object through an ObjectInfo manifest file. Used by the
|
|
# AppConfig handlers to give Outlook/Edge their schema-driven rows (the old code
|
|
# called Invoke-TranslateSection directly against #AppConfig*.json). $ManifestPath
|
|
# is a full path under Config\ObjectInfo\.
|
|
function Invoke-DocAppConfigManifest {
|
|
param($SettingsObject, [string]$ManifestPath, [DocumentationContext]$Context)
|
|
if (-not (Test-Path -LiteralPath $ManifestPath -PathType Leaf)) { return }
|
|
try {
|
|
$jsonObj = [IO.File]::ReadAllText($ManifestPath) | ConvertFrom-Json
|
|
}
|
|
catch {
|
|
Write-LogError "Failed to read AppConfig manifest $ManifestPath" $_.Exception
|
|
return
|
|
}
|
|
if (-not $jsonObj) { return }
|
|
$prev = $Context.CurrentObject
|
|
$Context.CurrentObject = $SettingsObject
|
|
try { Invoke-TranslateSection $SettingsObject $jsonObj $null }
|
|
catch { Write-LogError "Failed to translate AppConfig manifest $(Split-Path -Leaf $ManifestPath)" $_.Exception }
|
|
finally { $Context.CurrentObject = $prev }
|
|
}
|
|
|
|
function Invoke-TranslateSection {
|
|
param($Obj, $SectionObject, $ObjInfo, $Parent = $null)
|
|
|
|
$ctx = Get-CurrentDocumentationContext
|
|
|
|
# Reset/adjust propLevel based on whether we're a new walk or recursing
|
|
if ($null -eq $Parent -or $ctx.PropLevel -lt 0) {
|
|
$ctx.PropLevel = 0
|
|
}
|
|
elseif ($Parent -ne $script:_currentSectionParent) {
|
|
$ctx.PropLevel++
|
|
}
|
|
|
|
foreach ($prop in $SectionObject) {
|
|
$value = $null
|
|
$valueSet = $false
|
|
$useParentProp = $false
|
|
$payloadFile = $false
|
|
$skipChildren = $false
|
|
|
|
if (-not (Invoke-VerifyCondition $Obj $prop $ObjInfo)) {
|
|
Write-LogDebug "Condition returned false: $($prop.Condition | ConvertTo-Json -Depth 50 -Compress)"
|
|
continue
|
|
}
|
|
|
|
$Obj = Get-CustomPropertyObject $Obj $prop
|
|
$rawValue = $Obj."$($prop.entityKey)"
|
|
|
|
# ---- Section/category headers (dataType 8) ----
|
|
if ($prop.dataType -eq 8) {
|
|
if ($prop.nameResourceKey -eq 'LearnMore') { continue }
|
|
elseif ($prop.nameResourceKey -eq 'Empty') { $ctx.CurrentSubCategory = $null }
|
|
elseif ($prop.nameResourceKey -in $script:_categoriesToIgnore) { continue }
|
|
elseif ($prop.nameResourceKey) {
|
|
$key = if ($prop.nameResourceKey.Contains('.')) { $prop.nameResourceKey } else { "SettingDetails.$($prop.nameResourceKey)" }
|
|
$tmpStr = Get-LanguageString $key
|
|
if ($tmpStr -and $tmpStr.Length -lt 75) {
|
|
$ctx.CurrentSubCategory = $tmpStr
|
|
}
|
|
elseif ($tmpStr) {
|
|
Write-LogDebug "SubCategory ignored based on length: $tmpStr"
|
|
}
|
|
}
|
|
$ctx.PropLevel = -1
|
|
Invoke-ChildSections $Obj $prop
|
|
# A header without child sections leaves the -1 reset sentinel dangling;
|
|
# the next property's childSettings recursion would then reset to level 0
|
|
# instead of indenting one level under its parent row. Normalize here so
|
|
# only the header's own children get the flat-level reset.
|
|
if ($ctx.PropLevel -lt 0) { $ctx.PropLevel = 0 }
|
|
continue
|
|
}
|
|
|
|
# ---- Complex options (dataType 5) ----
|
|
if ($prop.dataType -eq 5) {
|
|
if ($prop.enabled -eq $false -and $ObjInfo.ShowDisabled -ne $true) { continue }
|
|
if (-not $prop.EntityKey -and $prop.nameResourceKey) {
|
|
$ctx.PropLevel = -1
|
|
$key = if ($prop.nameResourceKey.Contains('.')) { $prop.nameResourceKey } else { "SettingDetails.$($prop.nameResourceKey)" }
|
|
$ctx.CurrentSubCategory = Get-LanguageString $key
|
|
}
|
|
else {
|
|
$ctx.PropLevel--
|
|
}
|
|
foreach ($tmpObj in $Obj) {
|
|
Invoke-TranslateSection $tmpObj $prop.complexOptions $ObjInfo -Parent $prop
|
|
}
|
|
continue
|
|
}
|
|
|
|
# ---- Complex option based on sub-property (dataType 6) ----
|
|
if ($prop.dataType -eq 6) {
|
|
if ($prop.enabled -eq $false -and $ObjInfo.ShowDisabled -ne $true) { continue }
|
|
$ctx.PropLevel--
|
|
$propObj = $null
|
|
if ($prop.entityKey) { $propObj = $Obj.PSObject.Properties | Where-Object Name -EQ $prop.entityKey }
|
|
$iter = if ($null -ne $propObj) { $rawValue } else { $Obj }
|
|
foreach ($tmpObj in $iter) {
|
|
Invoke-TranslateSection $tmpObj $prop.complexOptions $ObjInfo -Parent $prop
|
|
}
|
|
continue
|
|
}
|
|
|
|
# ---- Skip-but-add-children label (dataType 9) ----
|
|
if ($prop.dataType -eq 9) {
|
|
$ctx.PropLevel--
|
|
Invoke-ChildSections $Obj $prop
|
|
continue
|
|
}
|
|
|
|
# ---- Information box: ignore (dataType 10) ----
|
|
if ($prop.dataType -eq 10) { continue }
|
|
|
|
# ---- Static-string label (dataType 101): language-id lookup ----
|
|
if ($prop.dataType -eq 101) {
|
|
if ($prop.value) {
|
|
$value = Get-LanguageString $prop.value
|
|
Add-PropertyInfo $prop $value $rawValue $rawValue
|
|
}
|
|
continue
|
|
}
|
|
|
|
# ---- Static value (dataType 107) ----
|
|
if ($prop.dataType -eq 107) {
|
|
if ($prop.value) {
|
|
Add-PropertyInfo $prop $prop.value $prop.value $prop.value
|
|
}
|
|
continue
|
|
}
|
|
|
|
# ---- Generic property path (dataType varies, requires entityKey) ----
|
|
if (-not [string]::IsNullOrEmpty($prop.entityKey)) {
|
|
$valueSet = ($null -ne $rawValue)
|
|
|
|
# Determine propValue (with defaults fallback). Old engine gates the
|
|
# unconfigured/default substitutions on $global:chk* UI checkboxes
|
|
# which default to UNCHECKED — meaning when a property is null on
|
|
# the input, the walker just uses null (and most translate primitives
|
|
# then either skip the row or emit "Not configured" via their own
|
|
# logic). My port honors that by gating on $ctx.Options.SetUnconfigured
|
|
# Value / SetDefaultValue (also default false).
|
|
$propValue = if ($null -ne $rawValue) { $rawValue }
|
|
elseif (-not [string]::IsNullOrEmpty($prop.unconfiguredValue) -and $ctx.Options.SetUnconfiguredValue) {
|
|
Add-NotConfiguredProperty $prop
|
|
$prop.unconfiguredValue
|
|
}
|
|
elseif (-not [string]::IsNullOrEmpty($prop.defaultValue) -and $ctx.Options.SetDefaultValue) {
|
|
$prop.defaultValue
|
|
}
|
|
elseif (-not [string]::IsNullOrEmpty($prop.emptyValueResourceKey) -and $ctx.Options.SetDefaultValue) {
|
|
Get-ObjectInfoResourceString $prop.emptyValueResourceKey -NoPrefix
|
|
}
|
|
else { $rawValue }
|
|
|
|
$addPropertyInfo = $true
|
|
$customValue = Get-CustomProfileValue $Obj $prop
|
|
|
|
if ($customValue -is [bool] -and $customValue -eq $false) {
|
|
continue
|
|
}
|
|
elseif (-not $customValue) {
|
|
|
|
# Linked certificate (dataType 4): live Graph navigationLink
|
|
# Stub offline — uses #CustomRef_ embedded data when present
|
|
if ($prop.dataType -eq 4) {
|
|
$useParentProp = $true
|
|
$cert = $null
|
|
if (-not $ctx.SourceTenantUnavailable -and (Test-DocumentationGraphAvailable)) {
|
|
try {
|
|
$url = $ctx.CurrentObject."$($prop.entityKey)@odata.navigationLink"
|
|
if ($url) {
|
|
# Most policies advertise the navigationLink even when no
|
|
# certificate is associated; the GET 404s. Cache the
|
|
# outcome on $ctx so the second walk of the same policy
|
|
# (the schema lists the same entityKey twice for the
|
|
# SCEP+PKCS+derived flavours) and any later policies in
|
|
# the bulk run skip a known-empty fetch.
|
|
if (-not $ctx.PSObject.Properties['_LinkedCertCache']) {
|
|
$ctx | Add-Member -MemberType NoteProperty -Name '_LinkedCertCache' -Value (@{}) -Force
|
|
}
|
|
if ($ctx._LinkedCertCache.ContainsKey($url)) {
|
|
$cert = $ctx._LinkedCertCache[$url]
|
|
}
|
|
else {
|
|
try {
|
|
$cert = Invoke-MSGraphAPI -Url $url -ODataMetadata 'minimal' -NoError
|
|
} catch { $cert = $null }
|
|
$ctx._LinkedCertCache[$url] = $cert
|
|
}
|
|
}
|
|
} catch { }
|
|
}
|
|
if ($cert) {
|
|
if ($cert.value -is [object[]]) {
|
|
$certs = @($cert.value | ForEach-Object { $_.displayName }) | Where-Object { $_ }
|
|
if ($certs.Count -gt 0) { $value = $certs -join $ctx.ObjectSeparator }
|
|
}
|
|
elseif ($cert.displayName) {
|
|
$value = $cert.displayName
|
|
}
|
|
$rawValue = $value
|
|
}
|
|
elseif ($ctx.CurrentObject.'@ObjectFromFile' -eq $true -or $ctx.SourceTenantUnavailable) {
|
|
$refKey = "#CustomRef_$($prop.entityKey)"
|
|
if ($ctx.CurrentObject.$refKey) {
|
|
$sep = $ctx.CurrentObject.$refKey.IndexOf('|:|')
|
|
$value = if ($sep -gt -1) { $ctx.CurrentObject.$refKey.Substring(0, $sep) } else { $ctx.CurrentObject.$refKey }
|
|
}
|
|
$rawValue = $value
|
|
}
|
|
}
|
|
# Multi-option based on boolean value where the property name IS the key (dataType 200)
|
|
elseif ($prop.dataType -eq 200) {
|
|
$value = Get-LanguageString $prop.entityKey
|
|
}
|
|
# Property missing on the object (and not "allowMissing")
|
|
elseif (-not $prop.allowMissing -and
|
|
$prop.entityKey -ne '.' -and
|
|
-not ($Obj.PSObject.Properties | Where-Object Name -EQ $prop.entityKey) -and
|
|
-not ($Obj.PSObject.Properties | Where-Object Name -EQ "$($prop.entityKey)@odata.navigationLink")) {
|
|
if ($prop.enabled -ne $false) {
|
|
Write-Log "Property with EntityKey $($prop.entityKey) is missing. Property will not be added!" 2
|
|
}
|
|
else {
|
|
Write-LogDebug "Disabled property with EntityKey $($prop.entityKey) is missing. Property will not be added!"
|
|
}
|
|
continue
|
|
}
|
|
else {
|
|
# NOTE: `continue` inside `switch` only goes to the next
|
|
# switch case match in PowerShell — it does NOT skip code
|
|
# after the switch. Cases that handle their own row emission
|
|
# (Option / Table) must set $addPropertyInfo = $false so the
|
|
# Add-PropertyInfo call below is skipped. (Earlier port used
|
|
# `continue` here and produced duplicate rows.)
|
|
switch ([int]$prop.dataType) {
|
|
0 { $value = Invoke-TranslateBoolean $Obj $prop }
|
|
1 {
|
|
# Base64 e.g. certificate data
|
|
$value = if ($prop.filenameEntityKey -and $Obj."$($prop.filenameEntityKey)") {
|
|
$Obj."$($prop.filenameEntityKey)"
|
|
} else {
|
|
$v = $Obj."$($prop.EntityKey)"
|
|
if ($v) { try { [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($v)) } catch { $v } } else { $v }
|
|
}
|
|
}
|
|
2 {
|
|
# Multiline string (often a base64-wrapped XML payload file)
|
|
if ($prop.filenameEntityKey -and $Obj."$($prop.filenameEntityKey)") {
|
|
$value = $Obj."$($prop.filenameEntityKey)"
|
|
$payloadFile = $true
|
|
}
|
|
else {
|
|
$v = $Obj."$($prop.EntityKey)"
|
|
$value = if ($v) { try { [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($v)) } catch { $v } } else { $v }
|
|
}
|
|
}
|
|
3 {
|
|
# Image — placeholder label; raw image data dropped (no consumer yet).
|
|
$value = if ($propValue) { 'Image file' } else { $null }
|
|
}
|
|
7 { $value = $propValue } # omaSettingDateTime — formatting deferred
|
|
11 { } # App picker — value left $null
|
|
12 {
|
|
# Multiline string / array
|
|
if (($propValue | Measure-Object).Count -gt 0) {
|
|
$value = $propValue -join $ctx.ObjectSeparator
|
|
}
|
|
}
|
|
13 { $value = Invoke-TranslateMultiOption $Obj $prop }
|
|
14 { $value = $propValue } # Int32
|
|
15 { $value = $propValue } # Int64
|
|
16 { Invoke-TranslateOption $Obj $prop | Out-Null; $addPropertyInfo = $false; $skipChildren = $true }
|
|
19 { Invoke-TranslateOption $Obj $prop | Out-Null; $addPropertyInfo = $false; $skipChildren = $true }
|
|
20 { $value = $propValue } # String
|
|
21 { Invoke-TranslateTable $Obj $prop; $addPropertyInfo = $false; $skipChildren = $true }
|
|
22 {
|
|
# Scale value e.g. "4 Years"
|
|
$value = $propValue
|
|
$scaleEntityKey = if ($Obj."$($prop.scaleEntityKey)") { $Obj."$($prop.scaleEntityKey)" } else { $prop.defaultScale }
|
|
if ($scaleEntityKey) {
|
|
$scaleOption = $prop.scaleOptions | Where-Object value -EQ $scaleEntityKey | Select-Object -First 1
|
|
if ($scaleOption.nameResourceKey) {
|
|
$value = '{0} {1}' -f $propValue, (Get-LanguageString "SettingDetails.$($scaleOption.nameResourceKey)")
|
|
}
|
|
}
|
|
}
|
|
100 { $value = Invoke-TranslateDuration $Obj $prop }
|
|
102 {
|
|
$culture = if ($propValue) { $propValue } else { $prop.unconfiguredValue }
|
|
$value = Get-CultureLanguageString $culture
|
|
}
|
|
103 {
|
|
# Boolean action but hide children on false
|
|
$value = Invoke-TranslateBoolean $Obj $prop
|
|
$skipChildren = ($propValue -eq $false)
|
|
}
|
|
104 {
|
|
$value = Invoke-TranslateMultiOptionBoolean $Obj $prop
|
|
$skipChildren = ($propValue -eq $false)
|
|
}
|
|
105 {
|
|
$value = Invoke-TranslateMultiOptionBoolean $Obj $prop $false
|
|
$skipChildren = ($propValue -eq $false)
|
|
}
|
|
106 {
|
|
# Array of cultures
|
|
$tmp = @()
|
|
foreach ($lng in $propValue) { $tmp += Get-CultureLanguageString $lng }
|
|
$value = $tmp -join $ctx.ObjectSeparator
|
|
}
|
|
108 {
|
|
# String with format
|
|
$value = $propValue
|
|
if ($prop.formatStringKey) {
|
|
$fmt = Get-LanguageString $prop.formatStringKey
|
|
if ($fmt) { $value = $fmt -f $propValue }
|
|
}
|
|
}
|
|
default {
|
|
$nameForLog = if ($prop.nameResourceKey) { Get-LanguageString "SettingDetails.$($prop.nameResourceKey)" } else { '' }
|
|
Write-Log "Unsupported property '$nameForLog' ($($prop.nameResourceKey)) for object property $($prop.entityKey). Type: $($prop.dataType)" 2
|
|
$value = $propValue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$value = $customValue.Value
|
|
$rawValue = $customValue.RawValue
|
|
$valueSet = ($null -ne $rawValue)
|
|
$addPropertyInfo = $customValue.AddPropertyInfo
|
|
}
|
|
|
|
if ($addPropertyInfo) {
|
|
$propForAdd = if ($useParentProp -and $Parent) { $Parent } else { $prop }
|
|
Add-PropertyInfo $propForAdd $value $rawValue
|
|
|
|
if ($payloadFile -and $Obj.payload) {
|
|
$tmpProp = [PSCustomObject]@{
|
|
nameResourceKey = 'uploadResult'
|
|
descriptionResourceKey = ''
|
|
entityKey = 'payloadData'
|
|
dataType = 20
|
|
booleanActions = 0
|
|
category = $prop.Category
|
|
}
|
|
$payloadValue = try { [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Obj.payload)) } catch { $Obj.payload }
|
|
Add-PropertyInfo $tmpProp $payloadValue $Obj.payload
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
Write-Log "No property entity key: $($prop.dataType) ($($prop.nameResourceKey))" 2
|
|
}
|
|
|
|
if ($valueSet -and -not $skipChildren) {
|
|
Invoke-ChildSections $Obj $prop
|
|
}
|
|
}
|
|
|
|
if ($null -ne $Parent -and $Parent -ne $script:_currentSectionParent -and $ctx.PropLevel -gt 0) {
|
|
$ctx.PropLevel--
|
|
}
|
|
}
|
|
|
|
# ---- Condition verifier (dataType-independent property gate) ----
|
|
function Invoke-VerifyCondition {
|
|
param($Obj, $Prop, $ObjInfo)
|
|
|
|
if (-not $Prop.Condition -or ($Prop.Condition.Expressions | Measure-Object).Count -eq 0) { return $true }
|
|
|
|
$type = if ($Prop.Condition.type -eq 'and') { 'and' } else { 'or' }
|
|
$defaultReturn = ($type -eq 'and')
|
|
|
|
foreach ($expression in $Prop.Condition.Expressions) {
|
|
if (-not $expression.property) { continue }
|
|
$tmpProp = $Obj.PSObject.Properties | Where-Object Name -EQ $expression.property
|
|
if (-not $tmpProp) {
|
|
if ($expression.ignoreMissing -eq $true) { continue }
|
|
return $false
|
|
}
|
|
|
|
$tmpRet = switch ($expression.operator) {
|
|
'null' { $null -eq $tmpProp.Value }
|
|
'ne' { $Obj."$($expression.property)" -ne $expression.value }
|
|
'gt' { $Obj."$($expression.property)" -gt $expression.value }
|
|
'ge' { $Obj."$($expression.property)" -ge $expression.value }
|
|
'lt' { $Obj."$($expression.property)" -lt $expression.value }
|
|
'le' { $Obj."$($expression.property)" -le $expression.value }
|
|
'like' { $Obj."$($expression.property)" -like $expression.value }
|
|
'notlike' { $Obj."$($expression.property)" -notlike $expression.value }
|
|
default {
|
|
if ($null -eq $expression.value) {
|
|
$null -ne $tmpProp.Value
|
|
}
|
|
else {
|
|
$Obj."$($expression.property)" -eq $expression.value
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($tmpRet -eq $true -and $type -eq 'or') { return $true }
|
|
if ($tmpRet -eq $false -and $type -eq 'and') { return $false }
|
|
}
|
|
return $defaultReturn
|
|
}
|
|
|
|
# ---- Culture-code -> language name ----
|
|
# Used by dataType 102 (Culture name) and 106 (Array of languages).
|
|
# Looks up Languages.<culture> in the loaded Strings-en.json; falls back to
|
|
# the OS culture's EnglishName.
|
|
function Get-CultureLanguageString {
|
|
param($Culture)
|
|
|
|
if (-not $Culture) { return $null }
|
|
try {
|
|
if ($Culture -eq 'os-default') { return Get-LanguageString 'Autopilot.OOBE.useOSDefaultLanguage' }
|
|
if ($Culture -eq 'user-select') { return Get-LanguageString 'Autopilot.OOBE.userSelect' }
|
|
|
|
# Force language strings to load by calling Get-LanguageString once
|
|
Get-LanguageString $null | Out-Null
|
|
|
|
$cache = Get-CacheObject "LanguageStrings_$($Culture)"
|
|
if (-not $cache) { $cache = Get-CacheObject 'LanguageStrings_en' }
|
|
if ($cache.Languages.$Culture) { return $cache.Languages.$Culture }
|
|
|
|
$parts = $Culture.Split('-')
|
|
if ($parts.Length -eq 3) {
|
|
$tri = "$($parts[0])-$($parts[1])"
|
|
if ($cache.Languages.$tri) { return $cache.Languages.$tri }
|
|
}
|
|
if ($parts.Length -gt 1 -and $cache.Languages."$($parts[0])") {
|
|
return $cache.Languages."$($parts[0])"
|
|
}
|
|
|
|
Write-Log "Translated language for $Culture not found" 2
|
|
return ([cultureinfo]$Culture).EnglishName
|
|
}
|
|
catch { return $null }
|
|
}
|