Private/Logic/ConvertTo-IntuneSettingInstance.ps1

# Copyright (c) 2026 Sandy Zeng. All rights reserved.
# Source-available. All rights reserved. See LICENSE file.

<#
    ConvertTo-IntuneSettingInstance.ps1 — Cleans a raw Graph setting instance into the shape Intune's import accepts.
 
    Author: Sandy Zeng
    Project: IntuneDiff
 
    Version History:
    1.0.0 Initial release.
#>


function ConvertTo-IntuneSettingInstance {
    <#
    .SYNOPSIS
        Cleans a raw Graph settingInstance (or settingInstanceTemplate) into the
        Settings-Catalog instance shape that Intune's import accepts.
 
    .DESCRIPTION
        PowerShell port of intuneExportUtils.cleanSettingInstance from IntuneDiff-v2.
        Handles choice/simple/group/collection settings and converts Security Baseline
        templates into instances. Recurses into children.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] $SettingInstance
    )

    if (-not $SettingInstance) { return $null }

    $odata = [string]$SettingInstance.'@odata.type'
    if ($odata -like '*Template*') {
        $odata = $odata -replace 'Template', ''
    }

    $cleaned = [ordered]@{
        '@odata.type'                       = $odata
        'settingDefinitionId'               = $SettingInstance.settingDefinitionId
        'settingInstanceTemplateReference'  = $null
    }

    # Choice
    if ($SettingInstance.choiceSettingValueTemplate) {
        $dv = $SettingInstance.choiceSettingValueTemplate.defaultValue
        $cleaned['choiceSettingValue'] = [ordered]@{
            'settingValueTemplateReference' = $null
            'value'                         = if ($dv -and $dv.settingDefinitionOptionId) { [string]$dv.settingDefinitionOptionId } else { '' }
            'children'                      = @(if ($dv -and $dv.children) { foreach ($c in $dv.children) { ConvertTo-IntuneSettingInstance -SettingInstance $c } } else { @() })
        }
    } elseif ($SettingInstance.choiceSettingValue) {
        $csv = $SettingInstance.choiceSettingValue
        $cleaned['choiceSettingValue'] = [ordered]@{
            'settingValueTemplateReference' = $null
            'value'                         = $csv.value
            'children'                      = @(if ($csv.children) { foreach ($c in $csv.children) { ConvertTo-IntuneSettingInstance -SettingInstance $c } } else { @() })
        }
    }

    # Simple
    if ($SettingInstance.simpleSettingValueTemplate) {
        $dv = $SettingInstance.simpleSettingValueTemplate.defaultValue
        $val = if ($null -ne $dv.constantValue) { $dv.constantValue } else { $dv.value }
        $cleaned['simpleSettingValue'] = [ordered]@{
            '@odata.type'                   = Get-SimpleSettingValueType -Value $val
            'settingValueTemplateReference' = $null
            'value'                         = $val
        }
    } elseif ($SettingInstance.simpleSettingValue) {
        $ssv = $SettingInstance.simpleSettingValue
        $cleaned['simpleSettingValue'] = [ordered]@{
            '@odata.type'                   = $ssv.'@odata.type'
            'settingValueTemplateReference' = $null
            'value'                         = $ssv.value
        }
    }

    # Simple collection
    if ($SettingInstance.simpleSettingCollectionValueTemplate) {
        $cleaned['simpleSettingCollectionValue'] = @(
            foreach ($item in @($SettingInstance.simpleSettingCollectionValueTemplate)) {
                $dv = $item.defaultValue
                $val = if ($null -ne $dv.constantValue) { $dv.constantValue } else { $dv.value }
                [ordered]@{
                    '@odata.type'                   = Get-SimpleSettingValueType -Value $val
                    'settingValueTemplateReference' = $null
                    'value'                         = $val
                }
            }
        )
    } elseif ($SettingInstance.simpleSettingCollectionValue) {
        $cleaned['simpleSettingCollectionValue'] = @(
            foreach ($item in @($SettingInstance.simpleSettingCollectionValue)) {
                [ordered]@{
                    '@odata.type'                   = $item.'@odata.type'
                    'settingValueTemplateReference' = $null
                    'value'                         = $item.value
                }
            }
        )
    }

    # Group collection
    if ($SettingInstance.groupSettingCollectionValueTemplate) {
        $cleaned['groupSettingCollectionValue'] = @(
            foreach ($item in @($SettingInstance.groupSettingCollectionValueTemplate)) {
                [ordered]@{
                    'settingValueTemplateReference' = $null
                    'children'                      = @(if ($item.children) { foreach ($c in $item.children) { ConvertTo-IntuneSettingInstance -SettingInstance $c } } else { @() })
                }
            }
        )
    } elseif ($SettingInstance.groupSettingCollectionValue) {
        $cleaned['groupSettingCollectionValue'] = @(
            foreach ($item in @($SettingInstance.groupSettingCollectionValue)) {
                [ordered]@{
                    'settingValueTemplateReference' = $null
                    'children'                      = @(if ($item.children) { foreach ($c in $item.children) { ConvertTo-IntuneSettingInstance -SettingInstance $c } } else { @() })
                }
            }
        )
    }

    # Choice collection
    if ($SettingInstance.choiceSettingCollectionValueTemplate) {
        $cleaned['choiceSettingCollectionValue'] = @(
            foreach ($item in @($SettingInstance.choiceSettingCollectionValueTemplate)) {
                $dv = $item.defaultValue
                [ordered]@{
                    'settingValueTemplateReference' = $null
                    'value'                         = if ($dv -and $dv.settingDefinitionOptionId) { [string]$dv.settingDefinitionOptionId } else { '' }
                    'children'                      = @(if ($dv -and $dv.children) { foreach ($c in $dv.children) { ConvertTo-IntuneSettingInstance -SettingInstance $c } } else { @() })
                }
            }
        )
    } elseif ($SettingInstance.choiceSettingCollectionValue) {
        $cleaned['choiceSettingCollectionValue'] = @(
            foreach ($item in @($SettingInstance.choiceSettingCollectionValue)) {
                [ordered]@{
                    'settingValueTemplateReference' = $null
                    'value'                         = $item.value
                    'children'                      = @(if ($item.children) { foreach ($c in $item.children) { ConvertTo-IntuneSettingInstance -SettingInstance $c } } else { @() })
                }
            }
        )
    }

    return [pscustomobject]$cleaned
}

function Get-SimpleSettingValueType {
    <#
    .SYNOPSIS
        Returns the Graph @odata.type string for a simple setting value based on its .NET type.
    #>

    param($Value)
    if ($Value -is [int] -or $Value -is [long] -or $Value -is [double]) {
        return '#microsoft.graph.deviceManagementConfigurationIntegerSettingValue'
    }
    if ($Value -is [bool]) {
        return '#microsoft.graph.deviceManagementConfigurationBooleanSettingValue'
    }
    return '#microsoft.graph.deviceManagementConfigurationStringSettingValue'
}

function ConvertTo-IntunePolicyExport {
    <#
    .SYNOPSIS
        Wraps a list of raw Graph settings into a Settings Catalog policy JSON
        document that can be re-imported into Intune.
 
    .PARAMETER RawSettings
        Array of raw setting objects from Graph (each containing settingInstance
        or settingInstanceTemplate). Duplicates (by reference) are removed.
 
    .PARAMETER PolicyName
        Name for the exported policy.
 
    .PARAMETER Description
        Optional description string.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [object[]]$RawSettings,
        [Parameter(Mandatory)] [string]$PolicyName,
        [string]$Description = ''
    )

    $seen = New-Object System.Collections.Generic.HashSet[object]
    $settings = New-Object System.Collections.Generic.List[object]
    $idx = 0
    foreach ($raw in $RawSettings) {
        if (-not $raw) { continue }
        if (-not $seen.Add($raw)) { continue }
        $instance = $null
        if ($raw.settingInstance) {
            $instance = ConvertTo-IntuneSettingInstance -SettingInstance $raw.settingInstance
        } elseif ($raw.settingInstanceTemplate) {
            $instance = ConvertTo-IntuneSettingInstance -SettingInstance $raw.settingInstanceTemplate
        }
        if (-not $instance) { continue }
        $settings.Add([pscustomobject][ordered]@{
            id              = "$idx"
            settingInstance = $instance
        })
        $idx++
    }

    $now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ', [System.Globalization.CultureInfo]::InvariantCulture)
    $template = [ordered]@{
        'templateId'             = ''
        'templateFamily'         = 'none'
        'templateDisplayName'    = $null
        'templateDisplayVersion' = $null
    }
    $envelope = [ordered]@{
        '@odata.context'       = 'https://graph.microsoft.com/beta/$metadata#deviceManagement/configurationPolicies/$entity'
        'createdDateTime'      = $now
        'creationSource'       = $null
        'description'          = $Description
        'lastModifiedDateTime' = $now
        'name'                 = $PolicyName
        'platforms'            = 'windows10'
        'priorityMetaData'     = $null
        'roleScopeTagIds'      = @('0')
        'settingCount'         = $settings.Count
        'technologies'         = 'mdm'
        'id'                   = [Guid]::NewGuid().ToString()
        'templateReference'    = [pscustomobject]$template
        'settings'             = @($settings.ToArray())
    }
    return [pscustomobject]$envelope
}