<#
.SYNOPSIS
Module for Intune tools

.DESCRIPTION
This module is for the Intune Tools View.

# Full ADMX reference can be found here (from 2007):
# http://download.microsoft.com/download/5/0/8/5081217f-4a2a-470e-a7fa-5976e40b0839/Group%20Policy%20ADMX%20Syntax%20Reference%20Guide.doc

# Schema documented 2017
# https://docs.microsoft.com/en-us/previous-versions/windows/desktop/policy/admx-schema

# ADMX schema reference can be found here
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpreg/6e10478a-e9e6-4fdc-a1f6-bdd9bd7f2209

.NOTES
  Author:         Mikael Karlsson
#>

$global:EMToolsViewObject = $null

function Get-ModuleVersion
{
    '1.0.5'
}

function Invoke-InitializeModule
{
    Add-ADMXRegClasses    

    Add-EMToolsViewItem

    # https://docs.microsoft.com/en-us/windows/client-management/mdm/win32-and-centennial-app-policy-configuration
    # ADMX ingestion cannot write to these paths:
    $script:unsupportedLocations = @('System','Software\Microsoft','Software\Policies\Microsoft')
    # With excemption for these paths:
    $script:unsupportedOverride = @('Software\Policies\Microsoft\Office','Software\Microsoft\Office','Software\Microsoft\Windows\CurrentVersion\Explorer','Software\Microsoft\Internet Explorer','software\policies\microsoft\shared tools\proofing tools','software\policies\microsoft\imejp','software\policies\microsoft\ime\shared','software\policies\microsoft\shared tools\graphics filters','software\policies\microsoft\windows\currentversion\explorer','software\policies\microsoft\softwareprotectionplatform','software\policies\microsoft\officesoftwareprotectionplatform','software\policies\microsoft\windows\windows search\preferences','software\policies\microsoft\exchange','software\microsoft\shared tools\proofing tools','software\microsoft\shared tools\graphics filters','software\microsoft\windows\windows search\preferences','software\microsoft\exchange','software\policies\microsoft\vba\security','software\microsoft\onedrive','software\Microsoft\Edge','Software\Microsoft\EdgeUpdate')
 
    $script:admxTemplate = @"
<policyDefinitions revision="1.0" schemaVersion="1.0">
    <categories>
        <category name="RegImport" />
    </categories>
    <policies>
        <policy name="" class="" displayName="" explainText="" presentation="" key="" valueName="">
            <parentCategory ref="RegImport" />
            <supportedOn ref="windows:SUPPORTED_Windows7" />
            <enabledValue>
                <decimal value="1" />
            </enabledValue>
            <disabledValue>
                <decimal value="0" />
            </disabledValue>

            <elements>
            </elements>
      </policy>
    </policies>
</policyDefinitions>      
"@    
}

function Add-EMToolsViewItem
{
    param($viewItem)

    if(-not $global:EMToolsViewObject)
    {
        $viewPanel = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerTools.xaml") -AddVariables
    
        if(-not $viewPanel) { return }

        #Add menu group and items
        $global:EMToolsViewObject = (New-Object PSObject -Property @{ 
            Title = "Intune Tools"
            Description = "Additional tools for managing Intune"
            ID = "EMTools"
            AuthenticationID = "MSAL"
            ViewPanel = $viewPanel 
            ItemChanged = { Show-EMTool }
            Activating = { Invoke-EMToolsActivatingView }
            Authentication = (Get-MSALAuthenticationObject)
            Authenticate = { Invoke-EMToolsAuthenticateToMSAL }
            AppInfo = (Get-GraphAppInfo "EMAzureApp" $global:DefaultAzureApp)
            SaveSettings = { Invoke-EMSaveSettings }
            Permissions = @()
        })

        Add-ViewObject $global:EMToolsViewObject

        Add-ViewItem (New-Object PSObject -Property @{
            Title = "ADMX Import"
            Id = "ADMXImport"
            ViewID = "EMTools"
            Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
            Icon="DeviceConfiguration"
            ShowViewItem = { Show-ADMXIngestion }
        })
        
        Add-ViewItem (New-Object PSObject -Property @{
            Title = "Reg Values"
            Id = "ADMXRegValues"
            ViewID = "EMTools"
            Permissons=@("DeviceManagementConfiguration.ReadWrite.All")
            Icon="DeviceConfiguration"
            ShowViewItem = { Show-ADMXRegValues }
        })
    }

    if($viewItem)
    {
        Add-ViewItem $viewItem
    }
}

function Invoke-EMToolsActivatingView
{

}

function Invoke-EMToolsAuthenticateToMSAL
{
    $global:EMToolsViewObject.AppInfo = Get-GraphAppInfo "EMAzureApp" $global:DefaultAzureApp
    Set-MSALCurrentApp $global:EMToolsViewObject.AppInfo
    $usr = (?? $global:MSALToken.Account.UserName (Get-Setting "" "LastLoggedOnUser"))
    if($usr)
    {
        & $global:msalAuthenticator.Login -Account $usr
    }
}

function Show-EMTool
{
    if($global:lstMenuItems.SelectedItem.ShowViewItem)
    {
        & $global:lstMenuItems.SelectedItem.ShowViewItem
    }
    else
    {
        $global:grdToolsMain.Children.Clear()
    }
}

#region ADMX functions
function Show-ADMXIngestion
{
    if(-not $script:admxPanel)
    {
        $script:admxPanel = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerToolsADMX.xaml") -AddVariables
    
        if(-not $script:admxPanel) { return }

        $global:btnADMXLoadADMX.Add_Click({        

            $of = [System.Windows.Forms.OpenFileDialog]::new()
            $of.Multiselect = $false
            $of.Filter = "ADMX Files (*.admx)|*.admx"
            $of.FileName = Get-Setting "Tools" "ADMXLastADMXFile"
            if($of.ShowDialog() -eq "OK")
            {            
                $script:currentADMXFile = [IO.FileInfo]$of.FileName
                Write-Status "Loading policy settings from $($script:currentADMXFile.Name)"
                Save-Setting "Tools" "ADMXLastADMXFile" $of.FileName
                Start-AdmxLoadFile $of.FileName            
                Write-Status ""
            }
        })

        $global:btnADMXLoadADML.Add_Click({
            $of = [System.Windows.Forms.OpenFileDialog]::new()
            $of.Multiselect = $false
            $of.Filter = "ADML Files (*.adml)|*.adml"
            $of.FileName = Get-Setting "Tools" "ADMXLastADMLFile"
            if($of.ShowDialog() -eq "OK")
            {
                Write-Status "Loading ADML policy $($of.FileName)"
                Save-Setting "Tools" "ADMXLastADMLFile" $of.FileName
                Invoke-LoadADMXSettings $of.FileName
                Write-Status ""
            }
        })

        $global:btnADMXImport.Add_Click({
            Write-Status "Import policy" 
            Import-ADMXPolicy
            Write-Status ""
        })
        
        $global:btnADMXPolicyNameRandom.Add_Click({
            $guid = [Guid]::NewGuid()
            if($global:txtADMXPolicyFileName.Text)
            {
                if($global:txtADMXPolicyFileName.Text.Split('_')[-1].Length -ne $guid.Guid.Length)
                {
                    $global:txtADMXPolicyFileName.Text = ($global:txtADMXPolicyFileName.Text + "_" + $guid.Guid)
                }
            }
            else
            {
                $global:txtADMXPolicyFileName.Text = $guid.Guid
            }
        })
        
        <#
        $global:txtADMXFilterSettings.Add_LostFocus({        
            Invoke-ADMXFilterPolicies $this
        })

        $global:txtADMXFilterSettings.Add_GotFocus({
            if($this.Tag -eq "1" -and $this.Text -eq "Filter") { $this.Text = "" }
            Invoke-ADMXFilterPolicies $this
        })
        
        $global:txtADMXFilterSettings.Add_TextChanged({
            Invoke-ADMXFilterPolicies $this
        })    
        Invoke-ADMXFilterPolicies $global:txtADMXFilterSettings

        #>

        $global:tvADMXCategories.Add_SelectedItemChanged({
            if($global:tvADMXCategories.SelectedItem.AllPolicies -eq $true)
            {
                $global:dgADMXCategoryPolicies.ColumnWidth = [System.Windows.Controls.DataGridLength]::Auto
                $global:dgADMXCategoryPolicies.Columns[0].Width = [System.Windows.Controls.DataGridLength]::Auto
                $global:dgADMXCategoryPolicies.Columns[1].Width = [System.Windows.Controls.DataGridLength]::Auto
                $global:dgADMXCategoryPolicies.Columns[2].Width = [System.Windows.Controls.DataGridLength]::Auto
                $global:dgADMXCategoryPolicies.Columns[2].Visibility = "Visible"
                $script:ocADMXSettingsList = [System.Collections.ObjectModel.ObservableCollection[object]]::new(@($global:tvADMXCategories.SelectedItem.Tag)) 
            }
            else
            {
                $global:dgADMXCategoryPolicies.ColumnWidth = [System.Windows.Controls.DataGridLength]"*"
                $global:dgADMXCategoryPolicies.Columns[0].Width = [System.Windows.Controls.DataGridLength]"10*"
                $global:dgADMXCategoryPolicies.Columns[1].Width = [System.Windows.Controls.DataGridLength]::Auto
                $global:dgADMXCategoryPolicies.Columns[2].Visibility = "Collapsed"
                $script:ocADMXSettingsList = [System.Collections.ObjectModel.ObservableCollection[object]]::new(@($script:admxPolicies | Where { $_.CategoryId -eq $global:tvADMXCategories.SelectedItem.Tag.CategoryName -and $_.SettingClass -eq $global:tvADMXCategories.SelectedItem.Tag.SettingClass}))
            }
            $global:dgADMXCategoryPolicies.ItemsSource = $script:ocADMXSettingsList 
        })

        $global:dgADMXCategoryPolicies.Add_MouseDoubleClick({        
            if(-not $global:dgADMXCategoryPolicies.SelectedItem) { return }
            Show-ADMXSettingProperties $global:dgADMXCategoryPolicies.SelectedItem
        })    

        $global:mnuADMXSettingsContextMenu.Add_Opened({
            $global:mnuADMXSettingEdit.IsEnabled = $null -ne $global:dgADMXCategoryPolicies.SelectedItem
        })
        
        $global:mnuADMXSettingEdit.Add_Click({
            if(-not $global:dgADMXCategoryPolicies.SelectedItem) { return }
            Show-ADMXSettingProperties $global:dgADMXCategoryPolicies.SelectedItem
        })

        $winADMLFile = "$($env:WinDir)\PolicyDefinitions\en-US\Windows.adml"
        if([IO.File]::Exists($winADMLFile))
        {
            [xml]$script:windowsADML = Get-Content $winADMLFile
        }
        else
        {
            Write-Log "Could not find Windows.adml. Support OS text might not be displayed correctly" 2    
        }
    }

    $global:grdToolsMain.Children.Clear()
    $global:grdToolsMain.Children.Add($script:admxPanel)
}

function Show-ADMXSettingProperties 
{
    param($settingObj)

    $script:settingsForm = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerToolsADMXSettingProperties.xaml") -AddVariables

    if(-not $script:settingsForm) { return }

    class ListValue
    {
        [string]$Key
        [string]$Value
    }  

    Set-ADMXSettingProperties $settingObj

    Add-XamlEvent $script:settingsForm "btnADMXSettingsOK" "add_click" ({
        Save-ADMXSettings
        
        Show-ModalObject
    })
    
    
    Add-XamlEvent $script:settingsForm "btnADMXPreviousSetting" "add_click" ({
        if($global:dgADMXCategoryPolicies.SelectedIndex -eq 0) { return }
        Save-ADMXSettings
        $global:dgADMXCategoryPolicies.SelectedIndex = $global:dgADMXCategoryPolicies.SelectedIndex - 1
        Set-ADMXSettingButtonsStatus
        Set-ADMXSettingProperties $global:dgADMXCategoryPolicies.SelectedItem
    })
    
    Add-XamlEvent $script:settingsForm "btnADMXNextSetting" "add_click" ({
        if($global:dgADMXCategoryPolicies.SelectedIndex -eq ($global:dgADMXCategoryPolicies.ItemsSource.Count - 1)) { return }         
        Save-ADMXSettings
        $global:dgADMXCategoryPolicies.SelectedIndex = $global:dgADMXCategoryPolicies.SelectedIndex + 1
        Set-ADMXSettingButtonsStatus
        Set-ADMXSettingProperties $global:dgADMXCategoryPolicies.SelectedItem
    })     

    Add-XamlEvent $script:settingsForm "btnADMXSettingsCancel" "add_click" ({
        $global:grdADMXElements.Children.Clear()
        $script:settingsForm = $null
        Show-ModalObject
    })
    
    $global:rbADMXSettingEnabled.Add_Checked({
        $script:curItemSettingStatus = 1
        Set-ADMXControlStatus
    })
    
    $global:rbADMXSettingDisabled.Add_Checked({
        $script:curItemSettingStatus = 0
        Set-ADMXControlStatus
    })
    
    $global:rbADMXSettingNotConfigured.Add_Checked({
        $script:curItemSettingStatus = $null
        Set-ADMXControlStatus
    })    

    $global:chkADMXManualConfig.Add_Click({
        Set-ADMXControlStatus
    })

    $global:tcADMXPolicyConfig.Add_SelectionChanged({
        param($sender, $e)

        if($e.AddedItems[0] -eq $global:tabADMXSettings)
        {            
            if($global:dgADMXSettings.SelectedItem.ManualConfig -ne 1)
            {
                $global:txtADMXSettings.Text = Get-ADMXSettingsString $script:settingsForm.DataContext
            }
        }
    })    

    Show-ModalForm $settingObj.Name $script:settingsForm -HideButtons
}

function Save-ADMXSettings
{
    if($global:chkADMXManualConfig.IsChecked)
    {
        $script:settingsForm.DataContext.PolicySettings = $global:txtADMXSettings.Text
    }
    else
    {
        $script:settingsForm.DataContext.PolicySettings = Get-ADMXSettingsString $script:settingsForm.DataContext
    }
    $script:settingsForm.DataContext.ManualConfig = (?: ($global:chkADMXManualConfig.IsChecked) 1 0)
    $script:settingsForm.DataContext.SettingStatus = $script:curItemSettingStatus
    Set-ADMXSettingStatusText $script:settingsForm.DataContext
    [System.Windows.Data.CollectionViewSource]::GetDefaultView($global:dgADMXCategoryPolicies.ItemsSource).Refresh()
    $global:grdADMXElements.Children.Clear()
}

function Set-ADMXSettingButtonsStatus
{
    $global:btnADMXPreviousSetting.IsEnabled = $global:dgADMXCategoryPolicies.SelectedIndex -gt 0
    $global:btnADMXNextSetting.IsEnabled = $global:dgADMXCategoryPolicies.SelectedIndex -lt ($global:dgADMXCategoryPolicies.ItemsSource.Count - 1)
}

function Set-ADMXSettingProperties
{
    param($settingObj)

    $global:grdADMXElements.Children.Clear()

    $script:curItemSettingStatus = $settingObj.SettingStatus
    if($settingObj.SettingStatus -eq 0)
    {
        $global:rbADMXSettingDisabled.IsChecked = $true
    }
    elseif($settingObj.SettingStatus -eq 1)
    {
        $global:rbADMXSettingEnabled.IsChecked = $true
    }
    else
    {
        $global:rbADMXSettingNotConfigured.IsChecked = $true
    }

    $global:txtADMXSettings.Text = $settingObj.PolicySettings

    $global:chkADMXManualConfig.IsChecked = $settingObj.ManualConfig

    if(-not $settingObj.PolicyDefinition)
    {
        $settingObj.PolicyDefinition = Format-XML $settingObj.Definition.OuterXml
    }

    if(-not $settingObj.supportedOn -and $settingObj.Definition.supportedOn.ref)
    {
        $settingObj.supportedOn = ($script:supportedOn | Where Id -eq $settingObj.Definition.supportedOn.ref.Split(':')[-1]).DisplayName
    }

    Set-ADMXElementsPanel $settingObj
    
    $script:settingsForm.DataContext = $settingObj

    Set-ADMXControlStatus
}

function Set-ADMXControlStatus
{
    $global:grdADMXElements.IsEnabled = ($script:curItemSettingStatus -eq 1 -and $global:chkADMXManualConfig.IsChecked -eq $false)
    $global:txtADMXSettings.IsReadOnly = ($script:curItemSettingStatus -ne 1 -or $global:chkADMXManualConfig.IsChecked -eq $false)    
}

function Start-AdmxLoadFile
{
    param($fileName)

    $script:xmlNS = $null
    $script:xmlNSPrefix = $null

    $script:admx = $null
    $script:admxPolicies = @()
    $script:supportedOn = @()
    $script:admxPoliciesHT = @{}
    $script:categoryPaths = @{}

    $global:txtADMXProfileName.Text = ""
    $global:txtADMXProfileDescription.Text = ""
    $global:txtADMXPolicyFileName.Text = ""
    $global:btnADMXLoadADML.IsEnabled = $false
    $global:txtADMXPolicyIngestName.Text = ""
    $global:txtADMXPolicyAppName.Text = $null

    $admxFI = [IO.FileInfo]$fileName   
    if($admxFI.Exists -eq $false) { return }

    $admlFile = [IO.Path]::Combine($admxFi.DirectoryName, "en-US\$($admxFi.BaseName).adml")
    if([IO.File]::Exists($admlFile) -eq $false)
    {
        $admlFile = [IO.Path]::Combine($admxFi.DirectoryName, "$($admxFi.BaseName).adml")
    }
       
    if([IO.File]::Exists($admlFile) -eq $false)
    {
        Write-Log "Could not find an ADML file" 2
        $admlFile = $null
    }

    try 
    {
        Write-Log "Load ADMX file $fileName"
        [xml]$script:admxXML = Get-Content $fileName
        
        $namespace = $script:admxXML.DocumentElement.NamespaceURI
        if($namespace)
        {
            $script:xmlNS = New-Object System.Xml.XmlNamespaceManager($script:admxXML.NameTable)
            $script:xmlNS.AddNamespace("ns", $namespace)
            $script:xmlNSPrefix = "ns:"
        }
        else
        {
            $script:xmlNS = $null
            $script:xmlNSPrefix = ""
        }

        $prefix = $script:admxXML.policyDefinitions.policyNamespaces.SelectSingleNode("$($script:xmlNSPrefix)target[@prefix]",$script:xmlNS)
        if($prefix)
        {
            if($prefix.namespace)
            {
                $policyId = $prefix.namespace.Split('.')[-1]
            }
            else
            {
                $policyId = $prefix.prefix
            }
        }
        else
        {
            $policyId = $tmpFI.BaseName -replace " ",""
            Write-Log "Failed to get policy id from XML. Using file base name" 2
        }
        $global:txtADMXPolicyAppName.Text = $policyId
        
        $global:txtADMXPolicyFileName.Text = $policyId
    }
    catch 
    {
        Write-LogError "Failed to load ADMX file" $_.Exception
    }

    $global:btnADMXLoadADML.IsEnabled = $true

    Invoke-LoadADMXSettings $admlFile
}

function Invoke-LoadADMXSettings
{
    param($admlFile)

    $script:lngADML = $null

    if($admlFile -and [IO.File]::Exists($admlFile))
    {
        try 
        {
            Write-Log "Load ADML file $admlFile"
            [xml]$script:lngADML = Get-Content $admlFile
        }
        catch 
        {
            Write-LogError "Failed to load ADML file $admlFile" $_.Exception
        }
    }

    $script:stringTable = @{}
    foreach($strNode in $script:lngADML.policyDefinitionResources.resources.stringTable.string)
    {
        $script:stringTable.Add($strNode.id, $strNode.'#text')
    }

    $script:supportedOn = @()
    foreach($polObj in $script:admxXML.policyDefinitions.supportedOn.definitions.definition)
    {
        $script:supportedOn += [PSCustomObject]@{
            Id=$polObj.Name
            DisplayName=(Get-ADMXADMLString $polObj)
        }
    }
    
    if($script:windowsADML)
    {
        foreach($winString in ($script:windowsADML.policyDefinitionResources.resources.stringTable.string | Where Id -like "SUPPORTED_*"))
        {
            $script:supportedOn += [PSCustomObject]@{
                Id=$winString.id
                DisplayName=$winString.'#text'
            }
        }
    }

    $devicePolicies = @()
    $userPolicies = @()

    foreach($polObj in $script:admxXML.policyDefinitions.policies.policy)
    {
        $displayName = $null
        $description = $null
        $category = $null
        $curSetting = $null

        if($polObj.parentCategory.ref)
        {
            $category = Get-ADMXCategoryNamePath $polObj.parentCategory.ref "/"
        }     

        $displayName = Get-ADMXADMLString $polObj  

        $description = Get-ADMXADMLString $polObj "explainText"
    
        if($polObj.Class -eq "Both")
        {
            $classArr = @("Device","User")
        }
        elseif($polObj.Class -eq "Machine")
        {
            $classArr = "Device"
        }

        $settingExists = $false
        foreach($class in $classArr)
        {
            #This will happen when loading a new ADML file
            $tmpName = ($polObj.Name + "_" + $class)
            if($script:admxPoliciesHT.ContainsKey($tmpName))
            {
                $curSetting = $script:admxPoliciesHT[$tmpName]
                
                foreach($tmpSetting in $curSetting)
                {
                    $settingExists = $true
                    $curSetting.Name = $displayName
                    $curSetting.Description = $description
                }            
            }
        }

        if($settingExists -eq $false)
        {
            if($polObj.Class -eq "Both")
            {
                $classArr = @("Device","User")
            }
            elseif($polObj.Class -eq "Machine")
            {
                $classArr = "Device"
            }
            else
            {
                $classArr = $polObj.Class
            }

            foreach($class in $classArr)
            {
                $newSetting = [PSCustomObject]@{
                    Name = $displayName
                    Description = $description
                    OMAURIName = $null
                    OMAURIDescription = $null
                    Category = $category
                    CategoryId = $polObj.parentCategory.ref
                    Id = $polObj.Name
                    Definition = $polObj
                    SettingStatus = $null
                    SettingStatusText = $null
                    PolicySettings = $null
                    PolicyDefinition = $null #Format-XML $polObj.OuterXml
                    ElementsPanel = $null
                    ManualConfig = $false
                    SettingClass = $class
                    SupportedOn = $null
                }

                $script:admxPoliciesHT.Add(($polObj.Name + "_" + $class), $newSetting)
                $script:admxPolicies += $newSetting
                if($newSetting.SettingClass -eq "User")
                {
                    $userPolicies += $newSetting
                }
                else
                {
                    $devicePolicies += $newSetting
                }
            }
        }
    }

    $script:admxPolicies | foreach-object { Set-ADMXSettingStatusText $_ }

    $script:admxPolicies = $script:admxPolicies | Sort -Property Name

    $global:tvADMXCategories.Items.Clear()

    $treeItems = @()

    $tvItem = [PSCustomObject]@{
        Name = "Computer Configuration"
        Children = @()
    }

    if($script:admxXML.policyDefinitions.policies)
    {
        $policies = $script:admxXML.policyDefinitions.policies.SelectNodes("$($script:xmlNSPrefix)policy[@class = 'Both' or @class = 'Machine']", $script:xmlNS)
        if($policies)
        {
            $categories = $policies.parentCategory | Select ref -Unique
            Add-ADMXCategories $categories $tvItem "Device"
        }
    }

    $treeItems += $tvItem

    $tvItem = [PSCustomObject]@{
        Name = "User Configuration"
        Children = @()
    }

    if($script:admxXML.policyDefinitions.policies)
    {
        $policies = $script:admxXML.policyDefinitions.policies.SelectNodes("$($script:xmlNSPrefix)policy[@class = 'Both' or @class = 'User']", $script:xmlNS)
        if($policies)
        {
            $categories = $policies.parentCategory | Select ref -Unique
            Add-ADMXCategories $categories $tvItem "User"
        }
    }

    $treeItems += $tvItem
    
    $treeItems | foreach-object { Add-ADMXCategoryTreeNode $_ $global:tvADMXCategories }

    if($devicePolicies.Count -gt 0)
    {
        $tvItem = [System.Windows.Controls.TreeViewItem]::new()
        $tvItem.Header = "All Policies"
        $tvItem.Tag = $devicePolicies
        $tvItem | Add-Member -MemberType NoteProperty -Name "AllPolicies" -Value $true
        $global:tvADMXCategories.Items[0].Items.Add($tvItem) | Out-Null  
    }

    if($userPolicies.Count -gt 0)
    {
        $tvItem = [System.Windows.Controls.TreeViewItem]::new()
        $tvItem.Header = "All Policies"
        $tvItem.Tag = $userPolicies
        $tvItem | Add-Member -MemberType NoteProperty -Name "AllPolicies" -Value $true
    }

    $global:tvADMXCategories.Items[1].Items.Add($tvItem) | Out-Null        
}

function Set-ADMXSettingStatusText
{
    param($settingObj)

    if($settingObj.SettingStatus -eq 0)
    {
        $settingObj.SettingStatusText = "Disabled"
    }
    elseif($settingObj.SettingStatus -eq 1)
    {
        $settingObj.SettingStatusText = "Enabled"
    }
    else
    {
        $settingObj.SettingStatusText = "Not Configured"
    }    
}
function Add-ADMXCategories
{
    param($categories, $parent, $settingClass)

    foreach($cat in $categories.ref)
    {
        $catPath = Get-ADMXCategoryIdPath $cat

        $tvObj = $parent
        foreach($catName in $catPath.Split('/'))
        {
            $curParent = $tvObj.Children | Where { $_.CategoryNode.name -eq $catName }
            if(-not $curParent)
            {
                $cat = $script:admxXML.policyDefinitions.categories.selectSingleNode("$($script:xmlNSPrefix)category[@name='$($catName)']",$script:xmlNS)
                $curParent += [PSCustomObject]@{
                    Name = Get-ADMXADMLString $cat 
                    CategoryName = $catName
                    CategoryNode = $cat
                    SettingClass = $settingClass             
                    Children = @()
                }

                $tvObj.Children += $curParent 
            }
            $tvObj = $curParent
        }
    }
}

Function Add-ADMXCategoryTreeNode
{
    Param($obj, $parent)

    $tvItem = [System.Windows.Controls.TreeViewItem]::new()
    $tvItem.Header = $obj.Name
    $tvItem.Tag = $obj
    $parent.Items.Add($tvItem) | Out-Null

    $obj.Children | Sort -Property Name | foreach-object { Add-ADMXCategoryTreeNode $_ $tvItem }
}

function Get-ADMXADMLString
{
    param($xmlNode, $property = "displayName")

    $propValue = $xmlNode.$property

    if(-not $script:lngADML -or -not $xmlNode.$property) { return $propValue}

    $tmpNode = $null

    if($xmlNode.$property.StartsWith("`$(") )
    {
        $tmp = $xmlNode.$property.SubString(2, $xmlNode.$property.Length - 3)
        $type,$strId = $tmp.Split('.')        
    }
    else
    {
        $strId =  $propValue
    }
    
    if($script:stringTable.ContainsKey($strId))
    {
        # Way quicker to use a hash table over querying all items
        $propValue = $script:stringTable[$strId]
    }
    else
    {
        $propValue = $tmpNode."#text"
    }

    $propValue
}

function Get-ADMXADMLPresentationString
{
    param($presentationInfo, $xmlNode)

    $tmp = $null

    if(-not $script:lngADML) { return $tmp }

    $presentationNode = $presentationInfo.selectSingleNode("./*[@refId='$($xmlNode.id)']")
    if($presentationNode)
    {
        $tmp = ?? $presentationNode.Label.'#text' $presentationNode.'#text'     
    }
    else
    {
        $tmp = $xmlNode.id
    }
    $tmp
}

function Get-ADMXCategoryIdPath
{
    param($categoryId, $delimiter = "/")

    $catObj = $script:admxXML.policyDefinitions.categories.selectSingleNode("$($script:xmlNSPrefix)category[@name='$($categoryId)']",$script:xmlNS)
    
    $categories = @()
    while($catObj)
    {
        $categories += $catObj.name
        if($catObj.parentCategory.ref)
        {
            $catObj = $script:admxXML.policyDefinitions.categories.selectSingleNode("$($script:xmlNSPrefix)category[@name='$($catObj.parentCategory.ref)']",$script:xmlNS)
        }
        else 
        {
            break
        }
    }
    
    [array]::Reverse($categories)

    $categories -join $delimiter
}

function Get-ADMXCategoryNamePath
{
    param($categoryId, $delimiter = "/")

    if($script:categoryPaths.ContainsKey($categoryId))
    {
        return $script:categoryPaths[$categoryId]
    }

    $catObj = $script:admxXML.policyDefinitions.categories.selectSingleNode("$($script:xmlNSPrefix)category[@name='$($categoryId)']",$script:xmlNS)
    
    $categories = @()
    while($catObj)
    {
        $categories += Get-ADMXADMLString $catObj
        if($catObj.parentCategory.ref)
        {
            $catObj = $script:admxXML.policyDefinitions.categories.selectSingleNode("$($script:xmlNSPrefix)category[@name='$($catObj.parentCategory.ref)']", $script:xmlNS)
        }
        else 
        {
            break
        }
    }
    
    [array]::Reverse($categories)

    $catPath = $categories -join $delimiter

    $script:categoryPaths.Add($categoryId, $catPath)

    $catPath
}

function Invoke-ADMXFilterPolicies 
{ 
    param($txtBox)

    $filter = $null
    
    if($txtBox.Text.Trim() -eq "" -and $txtBox.IsFocused -eq $false)
    {
        $txtBox.FontStyle = "Italic"
        $txtBox.Tag = 1
        $txtBox.Text = "Filter"
        $txtBox.Foreground="Lightgray"        
    }
    elseif($txtBox.Tag -eq "1" -and $txtBox.Text -eq "Filter" -and $txtBox.IsFocused -eq $false)
    {

    }
    else
    {
        $txtBox.FontStyle = "Normal"
        $txtBox.Tag = $null
        $txtBox.Foreground="Black"
        $txtBox.Background="White"

        if($txtBox.Text)
        {
            $filter = {
                param ($item)
                    return ($item.Name -match [regex]::Escape($txtBox.Text))
            }
        }         
    }

    if($global:dgADMXSettings.ItemsSource -is [System.Windows.Data.ListCollectionView]  -and $txtBox.IsFocused -eq $true)
    {
        # This causes odd behaviour with focus e.g. and item has to be clicked twice to be selected 
        $global:dgADMXSettings.ItemsSource.Filter = $filter
        #$global:dgADMXSettings.ItemsSource.Refresh()
    }
}

function Get-ADMXPresentationNode
{
    param($item)

    $presentation = $null

    if($item.Definition.presentation -and $script:lngADML)
    {
        if($item.Definition.presentation.StartsWith("`$(") )
        {
            $tmp = $item.Definition.presentation.SubString(2, $item.Definition.presentation.Length - 3)
            $type,$strId = $tmp.Split('.')
            $presentation = $script:lngADML.policyDefinitionResources.resources.presentationTable.presentation | Where Id -eq $strId #selectSingleNode("presentation[@id='$($strId)']")
        }
        else
        {
            $presentation = $script:lngADML.policyDefinitionResources.resources.presentationTable | Where Id -eq $item.Definition.presentation #.selectSingleNode("presentation[@id='$($item.Definition.presentation)']")
        }
    }
    $presentation
}

function Set-ADMXElementsPanel
{
    param($item)

    if(-not $item.Definition.elements) { return }

    if(-not $item.ElementsPanel)
    {
        $grd = [System.Windows.Controls.Grid]::new()
        $presentation = Get-ADMXPresentationNode $item
        $i = 0

        if($presentation)
        {
            # Policy node schema
            # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpreg/81a89003-5121-4216-b788-fde8daa71c78
            foreach($presentationNode in $presentation.ChildNodes)
            {
                $ctrl = $null

                $elementNode = $item.Definition.elements.ChildNodes | Where id -eq $presentationNode.refId #selectSingleNode("/*[@id='$($presentationNode.refId)']")
                if($presentationNode.Label.'#text')
                { $stringLabel = $presentationNode.Label.'#text' }
                elseif($presentationNode.Label)
                { $stringLabel = $presentationNode.Label }
                else
                { $stringLabel = $presentationNode.'#text' }               

                if($stringLabel -and $presentationNode.LocalName -ne "CheckBox") #$presentationNode.LocalName -eq "text")
                {
                    $ctrl = [System.Windows.Controls.TextBlock]::new()
                    $ctrl.Text = $stringLabel #$presentationNode."#text"                   
                    Add-GridObject $grd $ctrl
                }
                
                if($presentationNode.LocalName -eq "text")
                {
                    continue
                }

                if($ctrl)
                {
                    $ctrl.Margin = "0,5,0,0"
                }
                
                if($presentationNode.LocalName -eq "textbox")
                {
                    $ctrl = [System.Windows.Controls.TextBox]::new()
                    if($presentationNode.defaultValue -ne $null)
                    {
                        $ctrl.Text = $presentationNode.defaultValue
                    }                    
                }
                elseif($presentationNode.LocalName -eq "DecimalTextBox" -or
                        $presentationNode.LocalName -eq "LongDecimalTextBox" ) 
                {
                    $ctrl = Get-NumericUpDownControl $presentationNode.refId (?? $elementNode.minValue 0) (?? $elementNode.maxValue 9999) (?? $elementNode.SpinStep 1)
                    if(-not $ctrl) { continue }                    
                    if($presentationNode.defaultValue -ne $null)
                    {
                        $ctrl.Children[0].Text = $presentationNode.defaultValue
                    }
                }
                elseif($presentationNode.LocalName -eq "multiText")
                {
                    $ctrl = [System.Windows.Controls.TextBox]::new()
                    $ctrl.Height = 100
                    $ctrl.AcceptsReturn = $true
                }
                elseif($presentationNode.LocalName -eq "CheckBox")
                {
                    $ctrl = [System.Windows.Controls.CheckBox]::new()
                    $ctrl.Content = $stringLabel
                    if($presentationNode.defaultChecked -eq $true)
                    {
                        $ctrl.IsChecked = $true
                    }                    
                }
                elseif($presentationNode.LocalName -eq "ComboBox" -or
                        $presentationNode.LocalName -eq "DropdownList" )
                {
                    $ctrl = [System.Windows.Controls.ComboBox]::new()
                    $ctrl.DisplayMemberPath = "Name"
                    $ctrl.SelectedValuePath = "Value"

                    $valItems = @()
                    foreach($valItem in $elementNode.ChildNodes)
                    {
                        $displayName = Get-ADMXADMLString $valItem                        

                        if($valItem.value.decimal.value)
                        {
                            $value = $valItem.value.decimal.value
                        }
                        elseif($valItem.value.longDecimal)
                        {
                            $value = ?? $valItem.value.longDecimal.'#text' $valItem.value.longDecimal
                        }                        
                        elseif($valItem.value.string)
                        {
                            $value = ?? $valItem.value.string.'#text' $valItem.value.string
                        }
                        else
                        {
                            Write-Log "Unsupported value type for $($elementNode.Id): $($valItem.value.InnerXml)" 2
                            $value = "<SET MANUALLY!!!>"
                        }
                        $valItems += [PSCustomObject]@{
                            Name = $displayName 
                            Value = $value
                            } 
                    }

                    if($presentationNode.defaultItem -ne $null)
                    {
                        try
                        {
                            $ctrl.SelectedIndex = $presentationNode.defaultItem
                        }
                        catch {}
                    }

                    if($presentationNode.NoSort -ne "true")
                    {
                        $valItems = $valItems | Sort -Property Name
                    }

                    $ctrl.ItemsSource = $valItems
                }
                elseif($presentationNode.LocalName -eq "listBox")
                {
                    $ctrl = [System.Windows.Controls.DataGrid]::new()
                    $ctrl.CanUserAddRows = $true
                    $ctrl.CanUserDeleteRows = $true
                    $ctrl.CanUserSortColumns = $false
                    $ctrl.CanUserResizeRows = $false
                    $ctrl.AutoGenerateColumns = $false
                    $ctrl.ColumnWidth = [System.Windows.Controls.DataGridLength]"*"
                    
                    $column = [System.Windows.Controls.DataGridTextColumn]::new()
                    $column.Header = "Value Name"
                    $column.Width = [System.Windows.Controls.DataGridLength]"1*"
                    $binding = [System.Windows.Data.Binding]::new("Key")
                    $column.Binding = $binding
                    if($elementNode.explicitValue -ne "true")
                    {
                        $column.Visibility = "Collapsed"
                    }
                    $ctrl.Columns.Add($column)

                    $column = [System.Windows.Controls.DataGridTextColumn]::new()
                    $column.Header = "Value"
                    $column.Width = [System.Windows.Controls.DataGridLength]"1*"
                    $binding = [System.Windows.Data.Binding]::new("Value")
                    $column.Binding = $binding
                    $ctrl.Columns.Add($column)

                    $ctrl.ItemsSource = [System.Collections.Generic.List[ListValue]]::new()
                }
                else
                {
                    Write-Log "Unsupported object type in presentation: $($presentationNode.LocalName). Control: $presentationNode.refId" 2
                    continue
                }

                Add-GridObject $grd $ctrl

                if($presentationNode.refId)
                {
                    $ctrl.Tag = $elementNode
                    $ctrl.Name = $presentationNode.refId
                }
            }            
        }
        elseif($item.Definition.elements.ChildNodes)
        {            
            # This should NOT be used. Presentation settings should be defined in the ADML file
            Write-Log "No presentation settings found for $($item.Definition.Name)" 2
            foreach($elementNode in $item.Definition.elements.ChildNodes)
            {
                try
                {
                    $ctrl = $null
                    if($elementNode.LocalName -eq "text")
                    {
                        $ctrl = [System.Windows.Controls.TextBox]::new()
                    }
                    elseif($elementNode.LocalName -eq "multiText")
                    {
                        $ctrl = [System.Windows.Controls.TextBox]::new()
                        $ctrl.Height = 100
                        $ctrl.AcceptsReturn = $true
                    }         
                    elseif($elementNode.LocalName -eq "enum")
                    {
                        $ctrl = [System.Windows.Controls.ComboBox]::new()
                        $ctrl.DisplayMemberPath = "Name"
                        $ctrl.SelectedValuePath = "Value"

                        $valItems = @()
                        foreach($valItem in $elementNode.ChildNodes)
                        {
                            $displayName = Get-ADMXADMLString $valItem                        

                            if($valItem.value.decimal.value)
                            {
                                $value = $valItem.value.decimal.value
                            }
                            elseif($valItem.value.string)
                            {
                                $value = ?? $valItem.value.string.'#text' $valItem.value.string
                            }
                            else
                            {
                                Write-Log "Unsupported value type for $($elementNode.Id): $($valItem.value.InnerXml)" 2
                                $value = "<SET MANUALLY!!!>"
                            }
                            $valItems += [PSCustomObject]@{
                                Name = $displayName 
                                Value = $value
                                } 
                        }
                        $ctrl.ItemsSource = $valItems
                    }
                    elseif($elementNode.LocalName -eq "list")
                    {
                        $ctrl = [System.Windows.Controls.DataGrid]::new()
                        $ctrl.CanUserAddRows = $true
                        $ctrl.CanUserDeleteRows = $true
                        $ctrl.CanUserSortColumns = $false
                        $ctrl.CanUserResizeRows = $false
                        $ctrl.ColumnWidth = [System.Windows.Controls.DataGridLength]::Auto
                        $ctrl.AutoGenerateColumns = $false
                        $ctrl.ColumnWidth = [System.Windows.Controls.DataGridLength]"*"
                        
                        $column = [System.Windows.Controls.DataGridTextColumn]::new()
                        $column.Header = "Value Name"
                        $column.Width = [System.Windows.Controls.DataGridLength]"1*"
                        $binding = [System.Windows.Data.Binding]::new("Key")
                        $column.Binding = $binding
                        if($elementNode.explicitValue -ne "true")
                        {
                            $column.Visibility = "Collapsed"
                        }
                        $ctrl.Columns.Add($column)

                        $column = [System.Windows.Controls.DataGridTextColumn]::new()
                        $column.Header = "Value"
                        $column.Width = [System.Windows.Controls.DataGridLength]"1*"
                        $binding = [System.Windows.Data.Binding]::new("Value")
                        $column.Binding = $binding
                        $ctrl.Columns.Add($column)

                        $ctrl.ItemsSource = [System.Collections.Generic.List[ListValue]]::new()
                    }
                    elseif($elementNode.LocalName -eq "decimal")
                    {
                        $ctrl = [System.Windows.Controls.TextBox]::new()
                    }
                    elseif($elementNode.LocalName -eq "boolean")
                    {
                        $ctrl = [System.Windows.Controls.ComboBox]::new()
                    }
                    else
                    {
                        Write-Log "Element type not supported: $($elementNode.LocalName)" 2
                        continue
                    }

                    $displayName = Get-ADMXADMLPresentationString $presentation $elementNode
                                        
                    if($displayName)
                    {
                        $rd = [System.Windows.Controls.RowDefinition]::new()
                        $rd.Height = [double]::NaN #[System.Windows.GridLength]::Auto         
                        $grd.RowDefinitions.Add($rd)

                        $tb = [System.Windows.Controls.TextBlock]::new()
                        if($i -gt 0) { $tb.Margin = "0,5,0,0" }
                        $tb.Text = $displayName
                        $tb.SetValue([System.Windows.Controls.Grid]::RowProperty,$i)
                        $grd.Children.Add($tb)

                        $i++
                    }

                    $rd = [System.Windows.Controls.RowDefinition]::new()
                    $rd.Height = [double]::NaN 
                    $grd.RowDefinitions.Add($rd)

                    $ctrl.SetValue([System.Windows.Controls.Grid]::RowProperty,$i)
                    $ctrl.Tag = $elementNode
                    $ctrl.Name = $elementNode.Id
                    $grd.Children.Add($ctrl)

                    #$grd.RegisterName($ctrl.Name, $ctrl)

                    $i++
                }
                catch
                {
                    Write-LogError "Failed to add ADMX element $($elementNode.LocalName) with id $($elementNode.id)" $_.Exception
                }
            }
        }
        $rd = [System.Windows.Controls.RowDefinition]::new()
        #$rd.Height = [System.Windows.GridLength]::new(1, "Star")
        $grd.RowDefinitions.Add($rd)        
        $item.ElementsPanel = $grd
    }

    if($item.ElementsPanel)
    {
        $global:grdADMXElements.Children.Add($item.ElementsPanel)
        foreach($elementNode in $item.Definition.elements.ChildNodes)
        {            
            $ctrl = [System.Windows.LogicalTreeHelper]::FindLogicalNode($item.ElementsPanel, $elementNode.Id)
            if(-not $ctrl)
            {
                Write-Log "Could not find a control with id $($elementNode.Id)" 3
                continue
            }
            #$global:grdADMXElements.RegisterName($ctrl.Name, $ctrl)
        }
    }
}

function Get-ADMXSettingsString
{
    param($item)

    if(-not $item -or -not $item.Definition.elements) { return }

    if($script:curItemSettingStatus -ne 1)
    {
        $item.PolicySettings = $null
        return
    }

    $policySettings = @()
    foreach($elementNode in $item.Definition.elements.ChildNodes)
    {
        #$ctrl = $item.ElementsPanel.FindName($elementNode.Id)
        $ctrl = [System.Windows.LogicalTreeHelper]::FindLogicalNode($item.ElementsPanel, $elementNode.Id)
        if(-not $ctrl)
        {
            Write-Log "Could not find a control with id $($elementNode.Id)" 3
            continue
        }
        $ctrlValue = $null

        if($elementNode.LocalName -eq "text")
        {
            $ctrlValue = $ctrl.Text
        }
        elseif($elementNode.LocalName -eq "multiText")
        {
            $ctrlValue = $ctrl.Text -replace [Environment]::NewLine,"&#xF000;"
        }         
        elseif($elementNode.LocalName -eq "enum")
        {
            $ctrlValue = $ctrl.SelectedValue
        }
        elseif($elementNode.LocalName -eq "list")
        {
            $i = 1
            $keyValueArr = @()
            foreach($keyValue in $ctrl.ItemsSource)
            {
                if(-not $keyValue.Value) { continue }
                $keyValueArr += "$((?? $keyValue.Key $i))&#xF000;$($keyValue.Value)"
                $i++
            }
            $ctrlValue = $keyValueArr -join "&#xF000;"
        }
        elseif($elementNode.LocalName -eq "decimal" -or
            $ctrl.Tag.LocalName -eq "longDecimal")
        {            
            $ctrlValue = $ctrl.Children[0].Text
        }
        elseif($elementNode.LocalName -eq "boolean")
        {
            if($ctrl -is [System.Windows.Controls.CheckBox])
            {
                $ctrlValue = ?: $ctrl.IsChecked "1" "0"
                if($ctrl.IsChecked -eq $false)
                {
                    #continue # GPO setting skips unchecked checkbox. 
                }
            }
            elseif($ctrl -is [System.Windows.Controls.ComboBox])
            {
                $ctrlValue = $ctrl.SelectedValue
            }
            else
            {
                Write-Log "Boolean element type not supported: $($elementNode.LocalName)" 2
                continue
            }
        }
        else
        {
            Write-Log "Element type not supported: $($elementNode.LocalName)" 2
            continue
        }

        if(-not $ctrlValue)
        {
            if($elementNode.required -eq $true)
            {
                Write-Log "Required value is missing for $($elementNode.Id)" 3
            }
            else
            {
                Write-Log "Value not set for $($elementNode.Id). Value will not be added"
            }
            continue
        }

        $policySettings += "<data id=`"$($elementNode.Id)`" value=`"$($ctrlValue)`"/>"
    }
    $policySettings -join [Environment]::NewLine # Or ""?
}

function Get-ADMXCategoryOMAURIPath
{
    param($categoryId)

    $catObj = $script:admxXML.policyDefinitions.categories.selectSingleNode("$($script:xmlNSPrefix)category[@name='$($categoryId)']",$script:xmlNS)
    
    $categories = @()
    while($catObj)
    {
        $categories += $catObj.name
        $catObj = $script:admxXML.policyDefinitions.categories.selectSingleNode("$($script:xmlNSPrefix)category[@name='$($catObj.parentCategory.ref)']",$script:xmlNS)
    }
    
    [array]::Reverse($categories)

    $categories -join "~"
}

function Import-ADMXPolicy
{
    if(-not $global:txtADMXProfileName.Text.Trim())
    {
        [System.Windows.MessageBox]::Show("Profile Name name must be specified", "Error", "OK", "Error")
        return
    }

    if(-not $global:txtADMXPolicyFileName.Text.Trim())
    {
        [System.Windows.MessageBox]::Show("ADMX Policy Name name must be specified", "Error", "OK", "Error")
        return
    }

    $admxConfiguredSettings = @()

    foreach($admxPolicy in $($script:admxPolicies | Where { $_.SettingStatus -eq 0 -or $_.SettingStatus -eq 1 }))    
    {
        if($admxPolicy.SettingStatus -eq 0)
        {
            $policyValue = "<disabled />"            
        }
        else
        {
            $policyValue = "<enabled />"
            
            $strValue = $admxPolicy.PolicySettings

            if($strValue)
            {
                $policyValue += "`n`n$strValue"
            }            
        }

        $catPath = Get-ADMXCategoryOMAURIPath $admxPolicy.Definition.parentCategory.ref

        $omaUriPath = "./$($admxPolicy.SettingClass)/Vendor/MSFT/Policy/Config/$($global:txtADMXPolicyAppName.Text)~Policy~$catPath/$($admxPolicy.Definition.name)"
        
        if($admxPolicy.OMAURIDescription)
        {
            $desc = $admxPolicy.OMAURIDescription
        }
        elseif($admxPolicy.Description -and $admxPolicy.Description.Length -gt 1000)
        {
            $desc = $admxPolicy.Description.SubString(0,1000)
        }
        else
        {
            $desc = $admxPolicy.Description 
        }

        $admxConfiguredSettings += [PSCustomObject]@{
                        "@odata.type" = "#microsoft.graph.omaSettingString"
                        "displayName" = (?? $admxPolicy.OMAURIName $admxPolicy.Name)
                        "description" =  $desc
                        "omaUri" =  $omaUriPath
                        "value" = $policyValue
                        }        
    }

    $intuneObj = [PSCustomObject]@{
        "@odata.type" = "#microsoft.graph.windows10CustomConfiguration"
        "displayName" = $global:txtADMXProfileName.Text
        "omaSettings" =  @()
        "roleScopeTagIds" = @()
        "assignments" = @()
        }

    if($global:txtADMXProfileDescription.Text)
    {
        $intuneObj | Add-Member -MemberType NoteProperty -Name "description" -Value $global:txtADMXProfileDescription.Text
    }

    if($global:chkADMXPolicyIngest.IsChecked)
    {
        $intuneObj.omaSettings += [PSCustomObject]@{
            "@odata.type" = "#microsoft.graph.omaSettingString"
            "displayName" = (?? $global:txtADMXPolicyIngestName.Text ("$($script:currentADMXFile.Name) Ingestion"))
            "description" =  $null
            "omaUri" =  "./Device/Vendor/MSFT/Policy/ConfigOperations/ADMXInstall/$($global:txtADMXPolicyAppName.Text)/Policy/$($global:txtADMXPolicyFileName.Text)"
            "value" = (Format-XML $script:admxXML)
            }
    }

    $intuneObj.omaSettings += $admxConfiguredSettings

    $json = $intuneObj | ConvertTo-Json -Depth 20

    $obj = Invoke-GraphRequest -Url "/deviceManagement/deviceConfigurations" -Body $json -Method "POST"
    
    if($obj)
    {
        Write-Log "Device configuration profile '$($intuneObj.displayName)' created with id $($obj.Id)"
    }
    else
    {
        $text = "Failed to create device configuration profile '$($intuneObj.displayName)'"
        Write-Log $text 3
        [System.Windows.MessageBox]::Show(($text + [Environment]::NewLine + [Environment]::NewLine + 'Check log for errors'), "Error!", "OK", "Error")
    }    
}

#endregion

#region Reg Values
function Show-ADMXRegValues
{
    if(-not $script:frmADMXRegProfile)
    {
        $script:frmADMXRegProfile = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerToolsADMXRegValues.xaml") -AddVariables

        if(-not $script:frmADMXRegProfile) { return }

        $global:cbADMXRegPolicyType.ItemsSource = @(
            [PSCustomObject]@{
                Name = "Policy"
                Value = "Policy"
            },
            [PSCustomObject]@{
                Name = "Preference"
                Value = "Preference"
            }
        )
        
        $script:ADMXRegProfile = [ADMXRegProfile]::new()
        $script:frmADMXRegProfile.DataContext = $script:ADMXRegProfile

        $global:btnADMXRegClear.Add_Click({        
            $script:addedRegValues.Clear()
            $global:txtADMXRegProfileName.Text = ""
            $global:txtADMXRegProfileDescription.Text = ""
            $global:cbADMXRegPolicyType.SelectedValue = $null
        })

        $global:dgADMXRegAddedPolicies.Add_MouseDoubleClick({        
            if(-not $this.SelectedItem) { return }
            Show-ADMXRegSettings $this.SelectedItem
        })  

        $global:mnuADMXRegPoliciesContextMenu.Add_Opened({
            $global:mnuADMXRegPolicyEdit.IsEnabled = $null -ne $global:dgADMXRegAddedPolicies.SelectedItem
        })
        
        $global:mnuADMXRegPolicyEdit.Add_Click({
            if(-not $global:dgADMXRegAddedPolicies.SelectedItem) { return }
            Show-ADMXRegSettings $global:dgADMXRegAddedPolicies.SelectedItem
        })    

        $global:btnADMXAddRegValue.Add_Click({
            Show-ADMXRegSettings
        })
        
        $global:btnADMXRegImport.Add_Click({
            Write-Status "Import Reg Settings Policy"
            Import-ADMXRegProfile
            Write-Status ""
        })
    }
    
    $global:grdToolsMain.Children.Clear()
    $global:grdToolsMain.Children.Add($frmADMXRegProfile)
}

function Show-ADMXRegSettings 
{
    param($regProfile)
    
    $script:frmADMXRegPolicies = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerToolsADMXAddRegPolicy.xaml") -AddVariables

    if(-not $script:frmADMXRegPolicies) { return }

    $newValue = [ADMXRegPolicyElement]::new()

    if(-not $regProfile)
    {
        $regProfile = [ADMXRegPolicy]::new()
        $script:newRegPolicy = $true
    }
    else
    {
        $script:newRegPolicy = $false
    }

    $script:frmADMXRegPolicies.DataContext = [PSCustomObject]@{
        RegPolicy = $regProfile
        PolicyElement = $newValue
    }

    $regHives = @()

    # Should this be listed for preferences?
    $regHives += [PSCustomObject]@{
            Name = "HKEY_LOCAL_MACHINE"
            Value = "HKLM"
        }

    $regHives += [PSCustomObject]@{
        Name = "HKEY_CURRENT_USER"
        Value = "HKCU"
    }

    $global:cbADMXRegHive.ItemsSource = $regHives

    $global:cbADMXRegPolicyStatus.ItemsSource = @(
        [PSCustomObject]@{
            Name = "Enabled"
            Value = "Enabled"
        },
        [PSCustomObject]@{
            Name = "Disabled"
            Value = "Disabled"
        }      
    )

    $global:dgADMXRegAddedElements.Add_MouseDoubleClick({        
        if(-not $this.SelectedItem) { return }
        $global:btnADMXRegElementAdd.Visibility = "Collapsed"
        $global:btnADMXRegElementNew.Visibility = "Visible"            

        $selectedItem = $this.SelectedItem
        $tmp = $script:frmADMXRegPolicies.DataContext
        $script:frmADMXRegPolicies.DataContext = $null
        $tmp.PolicyElement = $selectedItem
        $script:frmADMXRegPolicies.DataContext = $tmp

        Set-ADMXRegAttributeControls

        [System.Windows.Forms.Application]::DoEvents()
    })  

    $global:cbADMXRegElementDataType.ItemsSource = @(
        [PSCustomObject]@{
            Name = "String"
            Value = "text"
        },
        [PSCustomObject]@{
            Name = "Multi-string"
            Value = "multiText"
        },
        [PSCustomObject]@{
            Name = "List"
            Value = "list"
        },
        [PSCustomObject]@{
            Name = "DWORD (32-bit)"
            Value = "decimal"
        }
        # Looks like longDecimal is not supported
        #,
        #[PSCustomObject]@{
        #    Name = "QWORD (64-bit)"
        #    Value = "longDecimal"
        #}        
    )

    Add-XamlEvent $script:frmADMXRegPolicies "cbADMXRegElementDataType" "Add_SelectionChanged"({
        Set-ADMXRegAttributeControls        
    })      

    Add-XamlEvent $script:frmADMXRegPolicies "btnADMXRegElementAdd" "add_click" ({        

        if($global:txtADMXRegElementKey.Text -and (Get-ADMXRegIsKeySupported $global:txtADMXRegElementKey.Text) -eq $false)
        {
            return
        }
        
        if(-not $script:frmADMXRegPolicies.DataContext.RegPolicy.Key)
        {
            [System.Windows.MessageBox]::Show("The Key value must be specified for the policy", "Error!","OK", "Error")
            return
        }
        
        $newValue = [ADMXRegPolicyElement]::new()
        $tmp = $script:frmADMXRegPolicies.DataContext
        $script:frmADMXRegPolicies.DataContext = $null
        $tmp.RegPolicy.PolicyElements.Add($tmp.PolicyElement)
        $tmp.PolicyElement = $newValue
        $script:frmADMXRegPolicies.DataContext = $tmp
        [System.Windows.Forms.Application]::DoEvents()
    })

    Add-XamlEvent $script:frmADMXRegPolicies "btnADMXRegElementNew" "add_click" ({
        $newValue = [ADMXRegPolicyElement]::new()
        $tmp = $script:frmADMXRegPolicies.DataContext
        $script:frmADMXRegPolicies.DataContext = $null
        #$tmp.RegPolicy.PolicyElements.Add($tmp.PolicyElement)
        $tmp.PolicyElement = $newValue
        $script:frmADMXRegPolicies.DataContext = $tmp
        [System.Windows.Forms.Application]::DoEvents()
        $global:btnADMXRegElementAdd.Visibility = "Visible"
        $this.Visibility = "Collapsed"
    })    

    Add-XamlEvent $script:frmADMXRegPolicies "btnADMXRegAddNew" "add_click" ({

        if((Get-ADMXRegIsKeySupported $global:txtADMXRegKey.Text) -eq $false)
        {
            return
        }

        if($script:newRegPolicy)
        {
            $script:frmADMXRegProfile.DataContext.ADMXPolicies.Add($script:frmADMXRegPolicies.DataContext.RegPolicy)
        }        

        Show-ModalObject
    })

    Add-XamlEvent $script:frmADMXRegPolicies "btnADMXRegCancel" "add_click" ({
        $script:frmADMXRegPolicies = $null
        Show-ModalObject
    })    
    Show-ModalForm "Add new reg policy" $script:frmADMXRegPolicies -HideButtons
}

function Set-ADMXRegAttributeControls
{
    if($global:cbADMXRegElementDataType.SelectedValue -eq "list" -or $global:cbADMXRegElementDataType.SelectedValue -eq "multiText")
    {
        $global:spADMXRegAttributeValueSeparator.Visibility = "Visible"
        $global:txtADMXRegAttributeValueSeparator.Visibility = "Visible"
    }
    else
    {
        $global:spADMXRegAttributeValueSeparator.Visibility = "Collapsed"
        $global:txtADMXRegAttributeValueSeparator.Visibility = "Collapsed"
    }


    if($global:cbADMXRegElementDataType.SelectedValue -eq "list")
    {
        $global:spADMXRegAttributeValuePrefix.Visibility = "Visible"
        $global:txtADMXRegAttributeValuePrefix.Visibility = "Visible"
    }
    else
    {
        $global:spADMXRegAttributeValuePrefix.Visibility = "Collapsed"
        $global:txtADMXRegAttributeValuePrefix.Visibility = "Collapsed"
    }

    $global:txtADMXRegElementValueName.IsReadOnly = ($global:cbADMXRegElementDataType.SelectedValue -eq "list")

    if($global:cbADMXRegElementDataType.SelectedValue -eq "text" -or 
        $global:cbADMXRegElementDataType.SelectedValue -eq "list")
    {
        $global:spADMXRegAttributeExpandable.Visibility = "Visible"
        $global:chkADMXRegAttributeExpandable.Visibility = "Visible"
    }
    else
    {
        $global:spADMXRegAttributeExpandable.Visibility = "Collapsed"
        $global:chkADMXRegAttributeExpandable.Visibility = "Collapsed"
    }

    if($global:cbADMXRegElementDataType.SelectedValue -eq "list")
    {
        $global:spADMXRegAttributeAdditive.Visibility = "Visible"
        $global:chkADMXRegAttributeAdditive.Visibility = "Visible"
    }
    else
    {
        $global:spADMXRegAttributeAdditive.Visibility = "Collapsed"
        $global:chkADMXRegAttributeAdditive.Visibility = "Collapsed"
    }    
}

function Get-ADMXRegIsKeySupported
{
    param($regKey)

    $tmpPath = $regKey.Trim('\')
    $tmpPath = $tmpPath + "\"

    $blockedSource = ""
    foreach($blockedPath in $script:unsupportedLocations)
    {
        if($tmpPath -like "$($blockedPath)\*")
        {
            $blockedSource = $blockedPath
            break
        }
    }

    if($blockedSource)
    {
        foreach($excemptionPath in $script:unsupportedOverride)
        {
            if($tmpPath -like "$($excemptionPath)\*")
            {
                $blockedSource = ""
                break
            }
        }
    }

    if($blockedSource)
    {
        [System.Windows.MessageBox]::Show("The registry key '$($global:txtADMXRegKey.Text)' is not supported" + [Environment]::NewLine + [Environment]::NewLine + "Blocked by root key: $blockedSource", "Unsupported reg ket", "OK", "Error")
        return $false
    }
    return $true
}

function Import-ADMXRegProfile
{    
    $xml = [xml]$script:admxTemplate

    $guidId = [Guid]::NewGuid()
    $rgPolicyFileName = ("RegPolicy_" + $guidId)

    $xml.policyDefinitions.categories.category.name = ($xml.policyDefinitions.categories.category.name + "_" + $guidId)

    $intuneObj = [PSCustomObject]@{
        "@odata.type" = "#microsoft.graph.windows10CustomConfiguration"
        "displayName" = $script:frmADMXRegProfile.DataContext.ProfileName
        "omaSettings" =  @()
        "roleScopeTagIds" = @()
        "assignments" = @()
        }

    $admxRegSettings = @()

    foreach($regPolicy in $script:frmADMXRegProfile.DataContext.ADMXPolicies)
    {
        if($regPolicy.PolicyStatus -eq "Enabled")
        {
            $OMAURIString = "<enabled />"
        }
        else
        {
            $OMAURIString = "<disabled />"
        }
        
        if($regPolicy.PolicyName)
        {
            # No space in name
            $policyName =  $regPolicy.PolicyName -replace " ","_"
        }
        else
        {
            $policyName = [Guid]::NewGuid().Guid
        }

        #build XML
        $newNode = $xml.policyDefinitions.policies.ChildNodes[0].CloneNode($true)
        $newNode.name = $policyName
        $newNode.class = (?: ($regPolicy.Hive -eq "HKLM") "Machine" "User")
        $newNode.displayName = "`$(string.$($policyName))"
        $newNode.presentation = "`$(presentation.$($policyName))"
        $newNode.key = $regPolicy.Key.Trim('\')
        $newNode.parentCategory.ref = $xml.policyDefinitions.categories.category.name        

        if(-not $regPolicy.StatusValueName)
        {
            $newNode.RemoveChild($newNode.SelectSingleNode("enabledValue"))
            $newNode.RemoveChild($newNode.SelectSingleNode("disabledValue"))
        }
        else
        {
            $newNode.valueName = $regPolicy.StatusValueName
        }

        if($regPolicy.PolicyElements -eq $null -or $regPolicy.PolicyElements.Count -eq 0)
        {
            $newNode.RemoveChild($newNode.SelectSingleNode("elements"))
        }
        else
        {
            $omaUriItems = @()
            foreach($element in $regPolicy.PolicyElements)
            {
                $child = $xml.CreateElement($element.DataType)
                if($element.DataType -eq "multitext" -or 
                        $element.DataType -eq "list")
                {
                    $splitter = ?? $global:txtADMXRegAttributeValueSeparator.Text ";"
                    $value = $element.Value -replace $splitter,"&#xF000;"
                }
                else
                {
                    $value = $element.Value
                }

                if($element.DataType -eq "list")
                {
                    # ToDo:
                    # Add support for additive in UI
                    Add-XMLAttribute $child "additive" "true"
                    Add-XMLAttribute $child "valuePrefix" $element.AttributePrefix
                }
                else
                {
                    Add-XMLAttribute $child "valueName" $element.ValueName
                }

                if(($element.DataType -eq "text" -or $element.DataType -eq "list") -and $element.AttributeExpandable)
                {
                    Add-XMLAttribute $child "expandable" "true"
                }

                if($element.DataType -eq "list" -and $element.AttributeAdditive)
                {
                    Add-XMLAttribute $child "additive" "true"
                }                

                if($element.AttributeSoft)
                {
                    Add-XMLAttribute $child "soft" "true"
                }

                if($element.DataType -eq "list")
                {
                    $keyStr = ?? $element.Key $regPolicy.Key
                    if($keyStr)
                    {                        
                        $idStr = $keyStr.Trim("\").Split('\')[-1]
                    }
                    else
                    {
                        $idStr = [Guid]::NewGuid().Guid
                    }
                    Add-XMLAttribute $child "id" ($idStr + "_Id")
                }
                else
                {
                    Add-XMLAttribute $child "id" ($element.ValueName + "_Id")
                }

                if($element.Key)
                {
                    Add-XMLAttribute $child "key" $element.Key.Trim('\')
                }

                $omaUriItems += "<data id=`"$(($element.ValueName + "_Id"))`" value=`"$($value)`"/>"
                
                $newNode.SelectSingleNode("elements").AppendChild($child) | Out-Null
            }
        }

        if($omaUriItems.Count -gt 0)
        {
            $OMAURIString = ($OMAURIString + [Environment]::NewLine + [Environment]::NewLine + ($omaUriItems -join [Environment]::NewLine)) 
        }

        $xml.policyDefinitions.SelectSingleNode("policies").AppendChild($newNode) | Out-Null

        $admxRegSettings += [PSCustomObject]@{
            "@odata.type" = "#microsoft.graph.omaSettingString"
            "displayName" = "Set $($policyName)"
            "omaUri" =  "./$((?: ($regPolicy.Hive -eq "HKLM") "Device" "User"))/Vendor/MSFT/Policy/Config/IntuneManagementReg~$(($script:frmADMXRegProfile.DataContext.PolicyType))~$($newNode.parentCategory.ref)/$($policyName)"
            "value" = $OMAURIString
        }    
    }

    $xml.policyDefinitions.SelectSingleNode("policies").RemoveChild($xml.policyDefinitions.policies.SelectSingleNode("policy")) | Out-Null    

    $intuneObj.omaSettings += [PSCustomObject]@{
        "@odata.type" = "#microsoft.graph.omaSettingString"
        "displayName" = "Reg ADMX Ingestion"
        "description" =  "This XML is generated by Intune Managemet tool"
        "omaUri" =  "./Device/Vendor/MSFT/Policy/ConfigOperations/ADMXInstall/IntuneManagementReg/$(($script:frmADMXRegProfile.DataContext.PolicyType))/$($rgPolicyFileName)"
        "value" = (Format-XML $xml)
        }    

    $intuneObj.omaSettings += $admxRegSettings

    $json = $intuneObj | ConvertTo-Json -Depth 20

    $obj = Invoke-GraphRequest -Url "/deviceManagement/deviceConfigurations" -Body $json -Method "POST"
    
    if($obj)
    {
        Write-Log "Custom profile '$($intuneObj.displayName)' created with id $($obj.Id)"
    }
    else
    {
        $text = "Failed to create device configuration profile '$($intuneObj.displayName)'"
        Write-Log $text 3
        [System.Windows.MessageBox]::Show(($text + [Environment]::NewLine + [Environment]::NewLine + 'Check log for errors'), "Error!", "OK", "Error")
    }
}

function Add-XMLAttribute
{
    param($xmlNode, $attribute, $Value)
    $xmlAttrib = $xmlNode.OwnerDocument.CreateAttribute($attribute)
    $xmlAttrib.Value = $Value
    $xmlNode.Attributes.Append($xmlAttrib)
}

function Add-ADMXRegClasses
{ 
    if (("ADMXRegPolicyElement" -as [type]))
    {
        return
    }

    $classDef = @"
    using System;
    using System.ComponentModel;
    using System.Collections.ObjectModel;

    public class ADMXRegPolicyElement : System.ComponentModel.INotifyPropertyChanged
    {
        public string DataType { get { return _dataType; } set { _dataType = value;  NotifyPropertyChanged("DataType"); NotifyPropertyChanged("DataTypeDisplayString");  } }
        private string _dataType = null;

        public string DataTypeDisplayString { get {
            if(DataType == "text")
                return "String";
            else if(DataType == "multiText")
                return "Multi-string";
            else if(DataType == "list")
                return "List";
            else if(DataType == "decimal")
                return "DWORD (32-bit)";
            else if(DataType == "longDecimal")
                return "QWORD (64-bit)";
            else
                return DataType;
        } }

        public string Key { get { return _key; } set { _key = value; NotifyPropertyChanged("Key"); } }
        private string _key;

        public string ValueName { get { return _valueName; } set { _valueName = value; NotifyPropertyChanged("ValueName"); } }
        private string _valueName;        

        public string Value { get { return _value; } set { _value = value; NotifyPropertyChanged("Value"); } }
        private string _value;

        public string AttributePrefix { get { return _attributePrefix; } set { _attributePrefix = value; NotifyPropertyChanged("AttributePrefix"); } }
        private string _attributePrefix;

        public bool AttributeSoft { get { return _attributeSoft; } set { _attributeSoft = value; NotifyPropertyChanged("AttributeSoft"); } }
        private bool _attributeSoft = false;        

        public bool AttributeExpandable { get { return _attributeExpandable; } set { _attributeExpandable = value; NotifyPropertyChanged("AttributeExpandable"); } }
        private bool _attributeExpandable = false;        

        public bool AttributeAdditive { get { return _attributeAdditive; } set { _attributeAdditive = value; NotifyPropertyChanged("AttributeAdditive"); } }
        private bool _attributeAdditive = false;        

        public event PropertyChangedEventHandler PropertyChanged;  

        // This method is called by the Set accessor of each property.  
        // The CallerMemberName attribute that is applied to the optional propertyName  
        // parameter causes the property name of the caller to be substituted as an argument.  
        private void NotifyPropertyChanged(string propertyName = "")  
        {  
            if(PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
        }        
    }

    public class ADMXRegPolicy
    {
        public string PolicyName {get; set;}
        public string PolicyStatus {get; set;}
        public string Hive {get; set;}
        public string Key {get; set;}
        public bool StatusValueEnabled { get; set;}
        public string StatusValueName { get; set;}
        public System.Collections.ObjectModel.ObservableCollection<ADMXRegPolicyElement> PolicyElements {get; set;}

        public ADMXRegPolicy()
        {
            PolicyElements = new System.Collections.ObjectModel.ObservableCollection<ADMXRegPolicyElement>();
            Hive = "HKLM";
            PolicyStatus = "Enabled";
            StatusValueEnabled = true;
        }
    }

    public class ADMXRegProfile
    {
        public string ProfileName {get; set;}
        public string ProfileDescription {get; set;}
        public string PolicyType {get; set;}
        public System.Collections.ObjectModel.ObservableCollection<ADMXRegPolicy> ADMXPolicies {get; set;}
        public string XmlString {get; set;}

        public ADMXRegProfile()
        {
            ADMXPolicies = new System.Collections.ObjectModel.ObservableCollection<ADMXRegPolicy>();
            PolicyType = "Policy";
        }
    }
"@
    [Reflection.Assembly]::LoadWithPartialName("mscorlib") | Out-Null
    [Reflection.Assembly]::LoadWithPartialName("System.ComponentModel") | Out-Null
    Add-Type -TypeDefinition $classDef
}

#endregion