REG2ADMX.VBS > Converted to Powershell / Gui added v2

Discussion in 'Scripting' started by pm67310, Dec 21, 2025.

  1. pm67310

    pm67310 MDL Guru

    Sep 6, 2011
    3,947
    3,242
    120
    #1 pm67310, Dec 21, 2025
    Last edited: Sep 25, 2026
    hello ,

    simple rewrite of reg2admx.vbs in powershell ( original author Mariano Sergio Cosentino : mscosentino-en.blogspot.com ) many site copied this script and remove original author !


    The reg2admx.ps1 script (rewrite by me !) is a tool used for converting Group Policy template settings stored in .reg (registry) format into ADMX (Administrative Template XML) format.

    Here's how it works:

    1. Group Policy Templates (.admx): These are XML files used in Windows environments to define settings that administrators can apply via Group Policy. They control various aspects of the operating system and software settings on computers in a network.

    2. Registry Files (.reg): These are files that store settings and configurations in the Windows registry. Group Policy settings can be stored in the registry, and administrators may use registry files to modify the system.
    Why use reg2admx.ps1?
    • When an administrator has custom registry-based policies (often from legacy systems or custom configurations), they might want to manage them using Group Policy, which makes it easier to apply and enforce settings across multiple computers.

    • The reg2admx.ps1 script helps convert the registry settings into a Group Policy ADMX template, so they can be managed and applied via Group Policy Management (GPM).
    Process:
    • The .reg file contains registry key-value pairs.

    • The reg2admx.ps1 script processes this file and generates corresponding .admx files, which can then be used in Group Policy Management.
    In simpler terms, it converts manual registry settings into a format that can be centrally managed by Group Policy, making the administration process more efficient, especially in large environments.

    Code:
    <#
    .SYNOPSIS
    REG_2_ADMXL.ps1 - Converts a .reg file to ADMX/ADML files in GPO format.
    
    PowerShell version by PM67310 (MDL), based on the original VBS version by Mariano Sergio Cosentino.
    
    .DESCRIPTION
        Single-file tool.
          - Double-click it (or run it with no parameters) -> opens a
            graphical interface (file picker, language, DisplayName,
            description).
          - Run it with -RegFileName / -Language -> works as a classic
            command-line tool, no window opens.
    
    .PARAMETER RegFileName
        Path to the .reg file to convert. If omitted, the GUI opens.
    
    .PARAMETER Language
        Language code (e.g.: fr-FR", "en-US", "en-GB", "es-ES", "es-AR", "de-DE", "it-IT",
          "pt-PT", "pt-BR", "nl-NL", "ru-RU", "ja-JP", "zh-CN", "ko-KR")
    
    .PARAMETER DisplayName
        GPO policy root display name. Must be a single attached word
        (letters/digits/hyphen/underscore, no spaces), e.g. "disable-aero".
        If omitted, it is derived automatically from the .reg file name
        (invalid characters replaced by "-"). Pass this parameter to use
        a different name instead.
    
    .PARAMETER Description
        (Optional) Free text describing what the GPO does. If omitted, the
        generated ADMX/ADML contains "No description provided."
    
    .EXAMPLE
        .\REG_2_ADMXL.ps1
        (opens the graphical interface)
    
    .EXAMPLE
        .\REG_2_ADMXL.ps1 -RegFileName "C:\myapp\myfile.reg" -Language "en-US" -DisplayName "disable-aero" -Description "Disable Aero Glass"
    #>
    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, Position = 0)]
        [string]$RegFileName,
    
        [Parameter(Mandatory = $false, Position = 1)]
        [string]$Language,
    
        [Parameter(Mandatory = $false, Position = 2)]
        [string]$DisplayName = "",
    
        [Parameter(Mandatory = $false, Position = 3)]
        [string]$Description = ""
    )
    
    # =======================================================================
    #region Engine (functions - no side effects when loaded)
    # =======================================================================
    
    $global:DefaultValuePatterns = @(
        '@',
        '(Default)',
        '(Valeur par défaut)',
        '(Valeur par d?faut)',
        '(Standard)',
        '(Predeterminado)',
        '(Predefinito)',
        '(Padrão)',
        '(默认)',
        '(既定)',
        '(기본값)'
    )
    
    function Write-Log {
        param([string]$Message)
        Write-Host $Message
        if ($null -ne $global:LogLines) {
            [void]$global:LogLines.Add($Message)
        }
    }
    
    function New-PolicyGuid {
        <#
        .SYNOPSIS
            Generates a GUID formatted for ADMX object names.
        .NOTES
            Named New-PolicyGuid (not New-Guid) to avoid shadowing the
            built-in Microsoft.PowerShell.Utility\New-Guid cmdlet.
        #>
        $guid = [System.Guid]::NewGuid().ToString()
        $guid = $guid.Replace("-", "_")
        return $guid
    }
    
    function Get-KeyName {
        param([string]$Path)
        $lastIndex = $Path.LastIndexOf("\")
        if ($lastIndex -gt 0) {
            return $Path.Substring($lastIndex + 1)
        }
        return "\\\\"
    }
    
    function Get-ParentGUID {
        param(
            [string]$Path,
            [string]$Name
        )
        $findPath = $Path.Replace("\$Name", "")
        $result = "XML_2_ADMXL"
        foreach ($category in $global:Categories) {
            if ($findPath -eq $category.FullPath) {
                $result = $category.GUID
                break
            }
        }
        return $result
    }
    
    function Get-RegistryClass {
        param([ref]$Key)
        $sKey = $Key.Value
        $tempClass = "Both"
    
        switch -Regex ($sKey) {
            "^HKEY_CURRENT_USER\\" { $sKey = $sKey.Substring(18); $tempClass = "User" }
            "^HKCU\\" { $sKey = $sKey.Substring(5); $tempClass = "User" }
            "^HKEY_LOCAL_MACHINE\\" { $sKey = $sKey.Substring(19); $tempClass = "Machine" }
            "^HKLM\\" { $sKey = $sKey.Substring(5); $tempClass = "Machine" }
            "^HKEY_CLASSES_ROOT" { $sKey = "SOFTWARE\Classes" + $sKey.Substring(17); $tempClass = "Both" }
            "^HKCR" { $sKey = "SOFTWARE\Classes" + $sKey.Substring(4); $tempClass = "Both" }
            "^HKEY_CURRENT_CONFIG" { $sKey = "SYSTEM\CurrentControlSet\Hardware Profiles\Current" + $sKey.Substring(19); $tempClass = "Machine" }
            "^HKCC" { $sKey = "SYSTEM\CurrentControlSet\Hardware Profiles\Current" + $sKey.Substring(4); $tempClass = "Machine" }
            "^HKEY_USERS\\" {
                $sKey = $sKey.Substring(11)
                $barIndex = $sKey.IndexOf("\")
                if ($barIndex -gt 0) { $sKey = $sKey.Substring($barIndex + 1) }
                $tempClass = "Both"
            }
            "^HKU\\" {
                $sKey = $sKey.Substring(4)
                $barIndex = $sKey.IndexOf("\")
                if ($barIndex -gt 0) { $sKey = $sKey.Substring($barIndex + 1) }
                $tempClass = "Both"
            }
        }
    
        $Key.Value = $sKey
        return $tempClass
    }
    
    function Test-IsDefaultValue {
        param([string]$ValueName)
        $trimmedName = $ValueName.Trim()
        if ($trimmedName -eq '@') { return $true }
        foreach ($pattern in $global:DefaultValuePatterns) {
            if ($trimmedName -eq $pattern -or $trimmedName -eq "`"$pattern`"") { return $true }
        }
        return $false
    }
    
    function Get-NormalizedDefaultValueName {
        param([string]$ValueName)
        if (Test-IsDefaultValue -ValueName $ValueName) { return "(Default)" }
        return $ValueName
    }
    
    function Test-DisplayNameValid {
        <#
        .SYNOPSIS
            Validates that a DisplayName is a single "attached" word:
            letters, digits, hyphens or underscores, no spaces, no other
            punctuation. Examples: "disable-aero" (valid), "disable aero" (invalid).
        #>
        param([string]$Name)
    
        if ([string]::IsNullOrWhiteSpace($Name)) { return $false }
        return ($Name -match '^[A-Za-z0-9]+([_-][A-Za-z0-9]+)*$')
    }
    
    function Get-DefaultDisplayNameFromRegFile {
        <#
        .SYNOPSIS
            Derives a valid DisplayName from the .reg file name when the
            user did not provide one explicitly.
        .DESCRIPTION
            Takes the .reg file name (without extension) and replaces any
            character that is not a letter/digit with "-", collapses
            repeated separators, and trims leading/trailing separators so
            the result always satisfies Test-DisplayNameValid.
        #>
        param([string]$Path)
    
        $base = [System.IO.Path]::GetFileNameWithoutExtension($Path)
    
        $clean = $base -replace '[^A-Za-z0-9]+', '-'
        $clean = $clean.Trim('-_')
    
        if ([string]::IsNullOrWhiteSpace($clean)) {
            $clean = "REG_2_ADMXL_Generated_Policy"
        }
    
        return $clean
    }
    
    function ConvertFrom-RegValueType {
        param(
            [string]$RawType,
            [string]$RawData
        )
        $result = @{ Type = "string"; Data = $RawData }
    
        switch -Regex ($RawType.ToLower()) {
            "^string$" { $result.Type = "string" }
            "^dword$" {
                $result.Type = "dword"
                if ($RawData -match "^[0-9a-fA-F]+$") {
                    try { $result.Data = [Convert]::ToInt64($RawData, 16).ToString() }
                    catch { $result.Data = $RawData }
                }
            }
            "^hex\(b\)$" { $result.Type = "qword"; $result.Data = $RawData }
            "^hex\(2\)$" { $result.Type = "expandsz"; $result.Data = Convert-HexToString -HexData $RawData }
            "^hex\(7\)$" { $result.Type = "multisz"; $result.Data = Convert-HexToMultiString -HexData $RawData }
            "^hex$" { $result.Type = "binary" }
            "^hex\(0\)$" { $result.Type = "none" }
            "^hex\(\d+\)$" { $result.Type = "binary" }
            default { $result.Type = "string" }
        }
    
        return $result
    }
    
    function Convert-HexToString {
        param([string]$HexData)
        try {
            $cleanHex = $HexData -replace '\s+', '' -replace ',', ''
            $bytes = @()
            for ($i = 0; $i -lt $cleanHex.Length; $i += 2) {
                if ($i + 1 -lt $cleanHex.Length) {
                    $bytes += [Convert]::ToByte($cleanHex.Substring($i, 2), 16)
                }
            }
            $result = [System.Text.Encoding]::Unicode.GetString($bytes)
            $result = $result.TrimEnd([char]0)
            return $result
        }
        catch { return $HexData }
    }
    
    function Convert-HexToMultiString {
        param([string]$HexData)
        try {
            $str = Convert-HexToString -HexData $HexData
            $str = $str -replace "`0`0$", ""
            $str = $str -replace "`0", "; "
            return $str
        }
        catch { return $HexData }
    }
    
    function Import-RegFile {
        param([string]$FilePath)
    
        $rootCategory = [PSCustomObject]@{
            Name       = $global:DisplayName
            FullPath   = $global:DisplayName
            GUID       = "XML_2_ADMXL"
            ParentGUID = ""
        }
        [void]$global:Categories.Add($rootCategory)
    
        $lines = @()
        $encodings = @(
            [System.Text.Encoding]::Unicode,
            [System.Text.Encoding]::UTF8,
            [System.Text.Encoding]::Default,
            [System.Text.Encoding]::GetEncoding(1252)
        )
    
        foreach ($encoding in $encodings) {
            try {
                $lines = [System.IO.File]::ReadAllLines($FilePath, $encoding)
                if ($lines.Count -gt 0 -and ($lines[0] -match "Windows Registry|REGEDIT4")) {
                    Write-Log "File encoding detected: $($encoding.EncodingName)"
                    break
                }
            }
            catch { continue }
        }
    
        if ($lines.Count -eq 0) {
            throw "Unable to read the file with any supported encoding."
        }
    
        $currentSubKey = ""
        $i = 0
    
        while ($i -lt $lines.Count) {
            $line = $lines[$i]
            $line = $line.TrimStart([char]0xFEFF, [char]0xFFFE)
            $line = $line.Trim()
    
            if ([string]::IsNullOrWhiteSpace($line) -or
                $line.StartsWith("Windows Registry") -or
                $line.StartsWith("REGEDIT4") -or
                $line.StartsWith(";")) {
                $i++; continue
            }
    
            if ($line.StartsWith("[") -and $line.EndsWith("]")) {
                $currentSubKey = $line.Substring(1, $line.Length - 2)
                if (-not $currentSubKey.StartsWith("-")) {
                    $category = [PSCustomObject]@{
                        Name       = Get-KeyName -Path $currentSubKey
                        FullPath   = $currentSubKey
                        GUID       = New-PolicyGuid
                        ParentGUID = Get-ParentGUID -Path $currentSubKey -Name (Get-KeyName -Path $currentSubKey)
                    }
                    [void]$global:Categories.Add($category)
                }
                $i++; continue
            }
    
            $isDefaultValue = $false
            $valueName = ""
            $valueType = ""
            $valueData = ""
    
            if ($line.StartsWith('@')) {
                $isDefaultValue = $true
                $valueName = "(Default)"
                $line = $line.Substring(1)
            }
            elseif ($line.StartsWith('"')) {
                $line = $line.Substring(1)
                $quoteIndex = -1
                $j = 0
                while ($j -lt $line.Length) {
                    if ($line[$j] -eq '"' -and ($j -eq 0 -or $line[$j - 1] -ne '\')) { $quoteIndex = $j; break }
                    $j++
                }
                if ($quoteIndex -gt 0) {
                    $valueName = $line.Substring(0, $quoteIndex)
                    $valueName = $valueName.Replace('\"', '"')
                    if (Test-IsDefaultValue -ValueName $valueName) {
                        $isDefaultValue = $true
                        $valueName = "(Default)"
                    }
                    $line = $line.Substring($quoteIndex + 1)
                }
                else { $i++; continue }
            }
            else { $i++; continue }
    
            $equalIndex = $line.IndexOf('=')
            if ($equalIndex -lt 0) { $i++; continue }
    
            $rightPart = $line.Substring($equalIndex + 1).Trim()
            if ($rightPart -eq '-') { $i++; continue }
    
            if ($rightPart.StartsWith('"')) {
                $valueType = "string"
                $rightPart = $rightPart.Substring(1)
                $endQuote = -1
                $j = 0
                while ($j -lt $rightPart.Length) {
                    if ($rightPart[$j] -eq '"' -and ($j -eq 0 -or $rightPart[$j - 1] -ne '\')) { $endQuote = $j; break }
                    $j++
                }
                if ($endQuote -ge 0) { $valueData = $rightPart.Substring(0, $endQuote) }
                else { $valueData = $rightPart.TrimEnd('"') }
                $valueData = $valueData.Replace('\"', '"')
                $valueData = $valueData.Replace('\\', '\')
            }
            else {
                $colonIndex = $rightPart.IndexOf(':')
                if ($colonIndex -gt 0) {
                    $valueType = $rightPart.Substring(0, $colonIndex).ToLower()
                    $valueData = $rightPart.Substring($colonIndex + 1)
                    if ($valueType.StartsWith("hex")) {
                        while ($valueData.TrimEnd().EndsWith('\')) {
                            $valueData = $valueData.TrimEnd().TrimEnd('\')
                            $i++
                            if ($i -lt $lines.Count) {
                                $nextLine = $lines[$i].Trim()
                                if (-not $nextLine.StartsWith('@') -and
                                    -not $nextLine.StartsWith('[') -and
                                    -not $nextLine.StartsWith('"') -and
                                    -not [string]::IsNullOrWhiteSpace($nextLine)) {
                                    $valueData += $nextLine
                                }
                                else { $i--; break }
                            }
                        }
                        $valueData = $valueData.Replace(" ", "")
                    }
                }
                else { $i++; continue }
            }
    
            $converted = ConvertFrom-RegValueType -RawType $valueType -RawData $valueData
    
            if ($global:Categories.Count -gt 0) {
                $lastCategory = $global:Categories[$global:Categories.Count - 1]
                $keyPath = $lastCategory.FullPath
                $keyClass = Get-RegistryClass -Key ([ref]$keyPath)
    
                $policy = [PSCustomObject]@{
                    Caption    = $valueName
                    GUID       = New-PolicyGuid
                    ParentGUID = $lastCategory.GUID
                    ValueName  = $valueName
                    ValueType  = $converted.Type
                    ValueData  = $converted.Data
                    Path       = $keyPath
                    Class      = $keyClass
                    IsDefault  = $isDefaultValue
                }
                [void]$global:Policies.Add($policy)
    
                Write-Log "  Imported: [$($converted.Type)] $valueName = $($converted.Data.Substring(0, [Math]::Min(50, $converted.Data.Length)))$(if($converted.Data.Length -gt 50){'...'})"
            }
    
            $i++
        }
    
        Write-Log "Import completed: $($global:Categories.Count - 1) categories, $($global:Policies.Count) policies"
    }
    
    function New-ADMXDocument {
        $xml = New-Object System.Xml.XmlDocument
        $declaration = $xml.CreateXmlDeclaration("1.0", "utf-8", $null)
        [void]$xml.AppendChild($declaration)
    
        $root = $xml.CreateElement("policyDefinitions")
        $root.SetAttribute("revision", "1.0")
        $root.SetAttribute("schemaVersion", "1.0")
        $root.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema")
        $root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
        [void]$xml.AppendChild($root)
    
        $policyNamespaces = $xml.CreateElement("policyNamespaces")
        [void]$root.AppendChild($policyNamespaces)
    
        $target = $xml.CreateElement("target")
        $target.SetAttribute("prefix", "Call4cloud")
        $target.SetAttribute("namespace", "MSC.Policies." + (New-PolicyGuid))
        [void]$policyNamespaces.AppendChild($target)
    
        $using = $xml.CreateElement("using")
        $using.SetAttribute("prefix", "windows")
        $using.SetAttribute("namespace", "Microsoft.Policies.Windows")
        [void]$policyNamespaces.AppendChild($using)
    
        $supersededAdm = $xml.CreateElement("supersededAdm")
        $supersededAdm.SetAttribute("fileName", "")
        [void]$root.AppendChild($supersededAdm)
    
        $resources = $xml.CreateElement("resources")
        $resources.SetAttribute("minRequiredRevision", "1.0")
        $resources.SetAttribute("fallbackCulture", $global:Language)
        [void]$root.AppendChild($resources)
    
        $categories = $xml.CreateElement("categories")
        [void]$root.AppendChild($categories)
    
        $policies = $xml.CreateElement("policies")
        [void]$root.AppendChild($policies)
    
        return $xml
    }
    
    function New-ADMLDocument {
        $xml = New-Object System.Xml.XmlDocument
        $declaration = $xml.CreateXmlDeclaration("1.0", "utf-8", $null)
        [void]$xml.AppendChild($declaration)
    
        $root = $xml.CreateElement("policyDefinitionResources")
        $root.SetAttribute("revision", "1.0")
        $root.SetAttribute("schemaVersion", "1.0")
        $root.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema")
        $root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
        [void]$xml.AppendChild($root)
    
        $displayNameNode = $xml.CreateElement("displayName")
        $displayNameNode.InnerText = "REG_2_ADMXL"
        [void]$root.AppendChild($displayNameNode)
    
        $description = $xml.CreateElement("description")
        $description.InnerText = "Source File: $($global:RegFileName)`n`n" + $global:PolicyDescription
        [void]$root.AppendChild($description)
    
        $resources = $xml.CreateElement("resources")
        [void]$root.AppendChild($resources)
    
        $stringTable = $xml.CreateElement("stringTable")
        [void]$resources.AppendChild($stringTable)
    
        $presentationTable = $xml.CreateElement("presentationTable")
        [void]$resources.AppendChild($presentationTable)
    
        return $xml
    }
    
    function Write-Categories {
        $admxCategoriesNode = $global:ADMXDoc.SelectSingleNode("policyDefinitions/categories")
        $admlStringNode = $global:ADMLDoc.SelectSingleNode("policyDefinitionResources/resources/stringTable")
    
        foreach ($category in $global:Categories) {
            $categoryNode = $global:ADMXDoc.CreateElement("category")
            $categoryNode.SetAttribute("name", "CAT_" + $category.GUID)
            $categoryNode.SetAttribute("displayName", "`$(string.CAT_" + $category.GUID + ")")
            $categoryNode.SetAttribute("explainText", "`$(string.CAT_" + $category.GUID + "_HELP)")
            [void]$admxCategoriesNode.AppendChild($categoryNode)
    
            if (-not [string]::IsNullOrEmpty($category.ParentGUID)) {
                $parentCategory = $global:ADMXDoc.CreateElement("parentCategory")
                $parentCategory.SetAttribute("ref", "CAT_" + $category.ParentGUID)
                [void]$categoryNode.AppendChild($parentCategory)
            }
    
            $stringNode = $global:ADMLDoc.CreateElement("string")
            $stringNode.SetAttribute("id", "CAT_" + $category.GUID)
            $stringNode.InnerText = $category.Name
            [void]$admlStringNode.AppendChild($stringNode)
    
            $stringHelpNode = $global:ADMLDoc.CreateElement("string")
            $stringHelpNode.SetAttribute("id", "CAT_" + $category.GUID + "_HELP")
            $stringHelpNode.InnerText = "This Category configures the Values located under the [" + $category.FullPath + "] Key.`n`n" + $global:PolicyDescription
            [void]$admlStringNode.AppendChild($stringHelpNode)
        }
    }
    
    function Write-Policies {
        $admxPoliciesNode = $global:ADMXDoc.SelectSingleNode("policyDefinitions/policies")
        $admlStringNode = $global:ADMLDoc.SelectSingleNode("policyDefinitionResources/resources/stringTable")
        $admlPresentationNode = $global:ADMLDoc.SelectSingleNode("policyDefinitionResources/resources/presentationTable")
    
        foreach ($policy in $global:Policies) {
            $policyNode = $global:ADMXDoc.CreateElement("policy")
            $policyNode.SetAttribute("name", "POL_" + $policy.GUID)
            $policyNode.SetAttribute("displayName", "`$(string.POL_" + $policy.GUID + ")")
            $policyNode.SetAttribute("explainText", "`$(string.POL_" + $policy.GUID + "_HELP)")
            $policyNode.SetAttribute("key", $policy.Path)
            $policyNode.SetAttribute("class", $policy.Class)
            $policyNode.SetAttribute("presentation", "`$(presentation.POL_" + $policy.GUID + ")")
            [void]$admxPoliciesNode.AppendChild($policyNode)
    
            $parentCategory = $global:ADMXDoc.CreateElement("parentCategory")
            $parentCategory.SetAttribute("ref", "CAT_" + $policy.ParentGUID)
            [void]$policyNode.AppendChild($parentCategory)
    
            $supportedOn = $global:ADMXDoc.CreateElement("supportedOn")
            $supportedOn.SetAttribute("ref", "windows:SUPPORTED_WindowsVista")
            [void]$policyNode.AppendChild($supportedOn)
    
            $elements = $global:ADMXDoc.CreateElement("elements")
            [void]$policyNode.AppendChild($elements)
    
            $typeDescription = switch ($policy.ValueType) {
                "string" { "REG_SZ (String)" }
                "expandsz" { "REG_EXPAND_SZ (Expandable String)" }
                "multisz" { "REG_MULTI_SZ (Multi-String)" }
                "dword" { "REG_DWORD (32-bit Integer)" }
                "qword" { "REG_QWORD (64-bit Integer)" }
                "binary" { "REG_BINARY (Binary Data)" }
                default { $policy.ValueType.ToUpper() }
            }
    
            $defaultIndicator = ""
            if ($policy.IsDefault) { $defaultIndicator = " [DEFAULT VALUE]" }
    
            $stringNode = $global:ADMLDoc.CreateElement("string")
            $stringNode.SetAttribute("id", "POL_" + $policy.GUID)
            $stringNode.InnerText = $policy.Caption + $defaultIndicator
            [void]$admlStringNode.AppendChild($stringNode)
    
            # Dynamic description: uses what the user typed, or a fallback
            # if the field was left empty (see Invoke-Reg2Admxl).
            $stringHelpNode = $global:ADMLDoc.CreateElement("string")
            $stringHelpNode.SetAttribute("id", "POL_" + $policy.GUID + "_HELP")
            $helpText = "This Policy configures the Value [" + $policy.ValueName + "] located under the [" + $policy.Path + "] Key.`n`n"
            $helpText += "Registry Value Type: $typeDescription`n"
            $helpText += "Original Value: [$($policy.ValueData)]`n`n"
            if ($policy.IsDefault) {
                $helpText += "Note: This is the DEFAULT value for this registry key.`n`n"
            }
            $helpText += "Description: " + $global:PolicyDescription
            $stringHelpNode.InnerText = $helpText
            [void]$admlStringNode.AppendChild($stringHelpNode)
    
            $presentation = $global:ADMLDoc.CreateElement("presentation")
            $presentation.SetAttribute("id", "POL_" + $policy.GUID)
            [void]$admlPresentationNode.AppendChild($presentation)
    
            # For the default value, "valueName" must be PRESENT but EMPTY.
            # It's a required attribute in the ADMX schema - omitting it
            # entirely causes gpedit to reject the file with a
            # "valueName is not defined" validation error - and an empty
            # string is what GPO interprets as "the key's default value".
            $registryValueName = $policy.ValueName
            if ($policy.IsDefault) {
                $registryValueName = ""
            }
    
            switch ($policy.ValueType.ToLower()) {
                "string" {
                    $textElement = $global:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "TXT_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($textElement)
    
                    $textBox = $global:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "TXT_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
    
                    $label = $global:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (REG_SZ)"
                    [void]$textBox.AppendChild($label)
    
                    $defaultValue = $global:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
                "expandsz" {
                    $textElement = $global:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "EXP_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    $textElement.SetAttribute("expandable", "true")
                    [void]$elements.AppendChild($textElement)
    
                    $textBox = $global:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "EXP_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
    
                    $label = $global:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (REG_EXPAND_SZ - supports %variables%)"
                    [void]$textBox.AppendChild($label)
    
                    $defaultValue = $global:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
                "multisz" {
                    $multiTextElement = $global:ADMXDoc.CreateElement("multiText")
                    $multiTextElement.SetAttribute("id", "MLT_" + $policy.GUID)
                    $multiTextElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($multiTextElement)
    
                    $multiTextBox = $global:ADMLDoc.CreateElement("multiTextBox")
                    $multiTextBox.SetAttribute("refId", "MLT_" + $policy.GUID)
                    $multiTextBox.InnerText = $policy.ValueName + " (REG_MULTI_SZ - one entry per line)"
                    [void]$presentation.AppendChild($multiTextBox)
                }
                "dword" {
                    $decimalElement = $global:ADMXDoc.CreateElement("decimal")
                    $decimalElement.SetAttribute("id", "DXT_" + $policy.GUID)
                    $decimalElement.SetAttribute("valueName", $registryValueName)
                    $decimalElement.SetAttribute("minValue", "0")
                    $decimalElement.SetAttribute("maxValue", "4294967295")
                    [void]$elements.AppendChild($decimalElement)
    
                    $decimalTextBox = $global:ADMLDoc.CreateElement("decimalTextBox")
                    $decimalTextBox.SetAttribute("refId", "DXT_" + $policy.GUID)
                    if ($policy.ValueData -match '^\d+$') {
                        $decimalTextBox.SetAttribute("defaultValue", $policy.ValueData)
                    }
                    $decimalTextBox.InnerText = $policy.ValueName + " (REG_DWORD)"
                    [void]$presentation.AppendChild($decimalTextBox)
                }
                "qword" {
                    $textElement = $global:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "QXT_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($textElement)
    
                    $textBox = $global:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "QXT_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
    
                    $label = $global:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (REG_QWORD - enter as hex bytes)"
                    [void]$textBox.AppendChild($label)
    
                    $defaultValue = $global:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
                { $_ -in "binary", "hex" } {
                    $textElement = $global:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "HXT_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($textElement)
    
                    $textBox = $global:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "HXT_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
    
                    $label = $global:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (REG_BINARY - hex format: 00,01,02,...)"
                    [void]$textBox.AppendChild($label)
    
                    $defaultValue = $global:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
                default {
                    Write-Log "Note: Unknown datatype '$($policy.ValueType)' for value '$($policy.ValueName)', treating as string"
    
                    $textElement = $global:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "UNK_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($textElement)
    
                    $textBox = $global:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "UNK_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
    
                    $label = $global:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (Unknown type: " + $policy.ValueType + ")"
                    [void]$textBox.AppendChild($label)
    
                    $defaultValue = $global:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
            }
        }
    }
    
    function Save-XMLFiles {
        $parentFolder = Split-Path -Parent $global:RegFileName
        if ([string]::IsNullOrEmpty($parentFolder)) {
            $parentFolder = (Get-Location).Path
        }
    
        $baseFileName = $global:DisplayName
    
        $admxName = Join-Path $parentFolder ($baseFileName + ".admx")
        $fileName = $baseFileName + ".adml"
    
        $langFolder = Join-Path $parentFolder $global:Language
        $admlName = Join-Path $langFolder $fileName
    
        if (-not (Test-Path $langFolder)) {
            New-Item -ItemType Directory -Path $langFolder -Force | Out-Null
            Write-Log "Created language folder: $langFolder"
        }
    
        $xmlWriterSettings = New-Object System.Xml.XmlWriterSettings
        $xmlWriterSettings.Indent = $true
        $xmlWriterSettings.IndentChars = "  "
        $xmlWriterSettings.Encoding = [System.Text.Encoding]::UTF8
    
        $xmlWriter = [System.Xml.XmlWriter]::Create($admxName, $xmlWriterSettings)
        $global:ADMXDoc.Save($xmlWriter)
        $xmlWriter.Close()
    
        $xmlWriter = [System.Xml.XmlWriter]::Create($admlName, $xmlWriterSettings)
        $global:ADMLDoc.Save($xmlWriter)
        $xmlWriter.Close()
    
        Write-Log ""
        Write-Log "Files saved successfully:"
        Write-Log "  ADMX: $admxName"
        Write-Log "  ADML: $admlName"
    
        return [PSCustomObject]@{
            AdmxPath = $admxName
            AdmlPath = $admlName
        }
    }
    
    function Invoke-Reg2Admxl {
        <#
        .SYNOPSIS
            Runs the full .reg -> ADMX/ADML conversion. Used by both the
            CLI path and the GUI in this script.
        #>
        [CmdletBinding()]
        param(
            [Parameter(Mandatory = $true)][string]$RegFileName,
            [Parameter(Mandatory = $true)][string]$Language,
            [Parameter(Mandatory = $false)][string]$DisplayName = "",
            [Parameter(Mandatory = $false)][string]$Description = ""
        )
    
        if (-not (Test-Path $RegFileName)) {
            throw "File not found. Unable to open $RegFileName."
        }
    
        # Reset shared state so this function can be called several times
        # in the same PowerShell session (typically from the GUI).
        $global:Categories = [System.Collections.ArrayList]@()
        $global:Policies = [System.Collections.ArrayList]@()
        $global:ADMXDoc = $null
        $global:ADMLDoc = $null
        $global:LogLines = [System.Collections.ArrayList]@()
    
        if ([string]::IsNullOrWhiteSpace($DisplayName)) {
            $DisplayName = Get-DefaultDisplayNameFromRegFile -Path $RegFileName
            Write-Log "No DisplayName provided -> using default derived from the .reg file name: $DisplayName"
        }
    
        if (-not (Test-DisplayNameValid -Name $DisplayName)) {
            throw "Invalid DisplayName '$DisplayName'. It must be a single attached word (letters, digits, '-' or '_', no spaces), e.g. 'disable-aero'."
        }
    
        $global:RegFileName = (Resolve-Path $RegFileName).Path
        $global:Language = $Language
        $global:DisplayName = $DisplayName
        $global:PolicyDescription = if ([string]::IsNullOrWhiteSpace($Description)) {
            "No description provided."
        } else {
            $Description.Trim()
        }
    
        Write-Log "Input file: $($global:RegFileName)"
        Write-Log "Language: $($global:Language)"
        Write-Log "Display Name: $($global:DisplayName)"
        Write-Log "Description: $($global:PolicyDescription)"
        Write-Log ""
    
        Write-Log "Importing registry file..."
        Import-RegFile -FilePath $global:RegFileName
    
        if ($global:Policies.Count -eq 0) {
            throw "No policies were imported from the registry file. Please check if the file format is correct."
        }
    
        Write-Log ""
        Write-Log "Creating ADMX/ADML documents..."
        $global:ADMXDoc = New-ADMXDocument
        $global:ADMLDoc = New-ADMLDocument
    
        Write-Log "Writing categories and policies..."
        Write-Categories
        Write-Policies
    
        $result = Save-XMLFiles
    
        Write-Log ""
        Write-Log "Conversion completed successfully!"
    
        return $result
    }
    
    #endregion
    
    # =======================================================================
    #region GUI
    # =======================================================================
    
    function Show-Reg2AdmxlGui {
    
        Add-Type -AssemblyName System.Windows.Forms
        Add-Type -AssemblyName System.Drawing
        [System.Windows.Forms.Application]::EnableVisualStyles()
    
        $form = New-Object System.Windows.Forms.Form
        $form.Text = "REG_2_ADMXL .reg to ADMX/ADML Converter by PM67310 MDL"
        $form.Size = New-Object System.Drawing.Size(560, 560)
        $form.StartPosition = "CenterScreen"
        $form.FormBorderStyle = "FixedDialog"
        $form.MaximizeBox = $false
        $form.Font = New-Object System.Drawing.Font("Segoe UI", 9)
    
        # --- Fichier .reg ---
        $lblReg = New-Object System.Windows.Forms.Label
        $lblReg.Text = ".reg file :"
        $lblReg.Location = New-Object System.Drawing.Point(15, 20)
        $lblReg.AutoSize = $true
        $form.Controls.Add($lblReg)
    
        $txtRegFile = New-Object System.Windows.Forms.TextBox
        $txtRegFile.Location = New-Object System.Drawing.Point(15, 42)
        $txtRegFile.Size = New-Object System.Drawing.Size(410, 24)
        $txtRegFile.ReadOnly = $true
        $form.Controls.Add($txtRegFile)
    
        $btnBrowse = New-Object System.Windows.Forms.Button
        $btnBrowse.Text = "Browse..."
        $btnBrowse.Location = New-Object System.Drawing.Point(435, 41)
        $btnBrowse.Size = New-Object System.Drawing.Size(95, 26)
        $form.Controls.Add($btnBrowse)
    
        $btnBrowse.Add_Click({
            $dlg = New-Object System.Windows.Forms.OpenFileDialog
            $dlg.Filter = "Registry File (*.reg)|*.reg|ALL File (*.*)|*.*"
            $dlg.Title = "Select .reg file"
            if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
                $txtRegFile.Text = $dlg.FileName
            }
        })
    
        # --- Langue ---
        $lblLang = New-Object System.Windows.Forms.Label
        $lblLang.Text = "Language (format culture, ex: en-US) :"
        $lblLang.Location = New-Object System.Drawing.Point(15, 82)
        $lblLang.AutoSize = $true
        $form.Controls.Add($lblLang)
    
        $cmbLanguage = New-Object System.Windows.Forms.ComboBox
        $cmbLanguage.Location = New-Object System.Drawing.Point(15, 104)
        $cmbLanguage.Size = New-Object System.Drawing.Size(200, 24)
        $cmbLanguage.DropDownStyle = "DropDown"
        @("fr-FR", "en-US", "en-GB", "es-ES", "es-AR", "de-DE", "it-IT",
          "pt-PT", "pt-BR", "nl-NL", "ru-RU", "ja-JP", "zh-CN", "ko-KR") |
            ForEach-Object { [void]$cmbLanguage.Items.Add($_) }
        $cmbLanguage.Text = "en-US"
        $form.Controls.Add($cmbLanguage)
    
        # --- DisplayName ---
        $lblDisplayName = New-Object System.Windows.Forms.Label
        $lblDisplayName.Text = "Name of the GPO (DisplayName) - optional :"
        $lblDisplayName.Location = New-Object System.Drawing.Point(15, 144)
        $lblDisplayName.AutoSize = $true
        $form.Controls.Add($lblDisplayName)
    
        $txtDisplayName = New-Object System.Windows.Forms.TextBox
        $txtDisplayName.Location = New-Object System.Drawing.Point(15, 166)
        $txtDisplayName.Size = New-Object System.Drawing.Size(515, 24)
        $form.Controls.Add($txtDisplayName)
    
        $lblDisplayNameHint = New-Object System.Windows.Forms.Label
        $lblDisplayNameHint.Text = "Leave blank to use the .reg filename, or enter one word without spaces."
        $lblDisplayNameHint.Location = New-Object System.Drawing.Point(15, 192)
        $lblDisplayNameHint.AutoSize = $true
        $lblDisplayNameHint.ForeColor = [System.Drawing.Color]::Gray
        $lblDisplayNameHint.Font = New-Object System.Drawing.Font("Segoe UI", 8)
        $form.Controls.Add($lblDisplayNameHint)
    
        # --- Description ---
        $lblDescription = New-Object System.Windows.Forms.Label
        $lblDescription.Text = "Description (optional) - what the GPO does :"
        $lblDescription.Location = New-Object System.Drawing.Point(15, 226)
        $lblDescription.AutoSize = $true
        $form.Controls.Add($lblDescription)
    
        $txtDescription = New-Object System.Windows.Forms.TextBox
        $txtDescription.Location = New-Object System.Drawing.Point(15, 248)
        $txtDescription.Size = New-Object System.Drawing.Size(515, 80)
        $txtDescription.Multiline = $true
        $txtDescription.ScrollBars = "Vertical"
        $form.Controls.Add($txtDescription)
    
        $lblDescriptionHint = New-Object System.Windows.Forms.Label
        $lblDescriptionHint.Text = "If empty, the ADMX/ADML file will contain : ""No description provided."""
        $lblDescriptionHint.Location = New-Object System.Drawing.Point(15, 330)
        $lblDescriptionHint.AutoSize = $true
        $lblDescriptionHint.ForeColor = [System.Drawing.Color]::Gray
        $lblDescriptionHint.Font = New-Object System.Drawing.Font("Segoe UI", 8)
        $form.Controls.Add($lblDescriptionHint)
    
        # --- Bouton Generer ---
        $btnGenerate = New-Object System.Windows.Forms.Button
        $btnGenerate.Text = "Generate ADMX / ADML"
        $btnGenerate.Location = New-Object System.Drawing.Point(15, 360)
        $btnGenerate.Size = New-Object System.Drawing.Size(515, 34)
        $btnGenerate.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
        $form.Controls.Add($btnGenerate)
    
        # --- Journal ---
        $lblLog = New-Object System.Windows.Forms.Label
        $lblLog.Text = "Event :"
        $lblLog.Location = New-Object System.Drawing.Point(15, 402)
        $lblLog.AutoSize = $true
        $form.Controls.Add($lblLog)
    
        $txtLog = New-Object System.Windows.Forms.TextBox
        $txtLog.Location = New-Object System.Drawing.Point(15, 424)
        $txtLog.Size = New-Object System.Drawing.Size(515, 90)
        $txtLog.Multiline = $true
        $txtLog.ReadOnly = $true
        $txtLog.ScrollBars = "Vertical"
        $txtLog.Font = New-Object System.Drawing.Font("Consolas", 8)
        $form.Controls.Add($txtLog)
    
        $btnGenerate.Add_Click({
            $txtLog.Clear()
    
            $regPath = $txtRegFile.Text.Trim()
            $language = $cmbLanguage.Text.Trim()
            $displayName = $txtDisplayName.Text.Trim()
            $description = $txtDescription.Text
    
            if ([string]::IsNullOrWhiteSpace($regPath) -or -not (Test-Path $regPath)) {
                [System.Windows.Forms.MessageBox]::Show(
                    "Please select a valid .reg file.",
                    "File missing",
                    [System.Windows.Forms.MessageBoxButtons]::OK,
                    [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
                return
            }
    
            if ([string]::IsNullOrWhiteSpace($language)) {
                [System.Windows.Forms.MessageBox]::Show(
                    "Select Language (ex: en-US).",
                    "Language missing",
                    [System.Windows.Forms.MessageBoxButtons]::OK,
                    [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
                return
            }
    
            if ([string]::IsNullOrWhiteSpace($displayName)) {
                $displayName = Get-DefaultDisplayNameFromRegFile -Path $regPath
                $txtDisplayName.Text = $displayName
            }
    
            if (-not (Test-DisplayNameValid -Name $displayName)) {
                [System.Windows.Forms.MessageBox]::Show(
                    "The GPO name must be a single, unbroken word with no spaces" +
                    "`n`nValid example : disable-aero" +
                    "`n`InValid example : disable aero",
                    "Gpo Name Invalid Format",
                    [System.Windows.Forms.MessageBoxButtons]::OK,
                    [System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null
                $txtDisplayName.Focus()
                return
            }
    
            $form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
            $btnGenerate.Enabled = $false
    
            try {
                $result = Invoke-Reg2Admxl -RegFileName $regPath -Language $language `
                    -DisplayName $displayName -Description $description
    
                $txtLog.Text = ($global:LogLines -join "`r`n")
                $txtLog.SelectionStart = $txtLog.Text.Length
                $txtLog.ScrollToCaret()
    
                [System.Windows.Forms.MessageBox]::Show(
                    "Successful conversion !`n`nADMX : $($result.AdmxPath)`nADML : $($result.AdmlPath)",
                    "Succes",
                    [System.Windows.Forms.MessageBoxButtons]::OK,
                    [System.Windows.Forms.MessageBoxIcon]::Information) | Out-Null
            }
            catch {
                if ($global:LogLines) {
                    $txtLog.Text = ($global:LogLines -join "`r`n")
                }
                [System.Windows.Forms.MessageBox]::Show(
                    "Error : $($_.Exception.Message)",
                    "Conversion Failed",
                    [System.Windows.Forms.MessageBoxButtons]::OK,
                    [System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null
            }
            finally {
                $form.Cursor = [System.Windows.Forms.Cursors]::Default
                $btnGenerate.Enabled = $true
            }
        })
    
        [void]$form.ShowDialog()
    }
    
    #endregion
    
    # =======================================================================
    #region Entry point
    # =======================================================================
    
    if ([string]::IsNullOrWhiteSpace($RegFileName)) {
        # No .reg file passed on the command line -> open the GUI.
        Show-Reg2AdmxlGui
    }
    else {
        # CLI mode: RegFileName was supplied -> run headless, no window.
        if ([string]::IsNullOrWhiteSpace($Language)) {
            Write-Error "The -Language parameter is required in command-line mode."
            exit 1
        }
    
        Write-Log "=============================================="
        Write-Log "  REG_2_ADMXL - Registry to ADMX/ADML Converter"
        Write-Log "=============================================="
        Write-Log ""
    
        try {
            Invoke-Reg2Admxl -RegFileName $RegFileName -Language $Language -DisplayName $DisplayName -Description $Description | Out-Null
            Write-Log ""
            Write-Log "Done."
        }
        catch {
            Write-Error $_.Exception.Message
            exit 1
        }
    }
    
    #endregion
    

    Code:
    <#
    .SYNOPSIS
        REG_2_ADMXL.ps1 - Converts a .reg file to ADMX/ADML files for GPO
    
    .DESCRIPTION
        This script reads a .reg file and generates a pair of ADMX/ADML files
        allowing configuration of these settings via Group Policy Objects (GPO).
     
        Supports the following formats:
        - Default value: @="value", "(Default)"="value", "(Valeur par défaut)"="value"
        - Types: REG_SZ, REG_EXPAND_SZ, REG_DWORD, REG_QWORD, REG_BINARY, REG_MULTI_SZ
    
    .PARAMETER RegFileName
        Path to the .reg file to convert
    
    .PARAMETER Language
        Language code (e.g.: en-US, fr-FR, es-AR)
    
    .PARAMETER DisplayName
        (Optional) Display name for the GPO policy root
    
    .EXAMPLE
        .\REG_2_ADMXL.ps1 -RegFileName "C:\myapp\myfile.reg" -Language "en-US" -DisplayName "MY APP Policies"
    
    .EXAMPLE
        .\REG_2_ADMXL.ps1 -RegFileName "C:\myapp\myfile.reg" -Language "fr-FR" -DisplayName "MY APP Policies"
    
    .NOTES
        PowerShell 5.1 conversion of the original VBS script REG_2_ADMXL.vbs
        Added support for default values in French and English
    #>
    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$RegFileName,
     
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$Language,
     
        [Parameter(Mandatory = $false, Position = 2)]
        [string]$DisplayName = "REG_2_ADMXL Generated Policy"
    )
    
    #region Global Variables
    
    # Categories table
    # Index 0: (unused)
    # Index 1: Name
    # Index 2: Full path
    # Index 3: GUID
    # Index 4: Parent GUID
    $script:Categories = [System.Collections.ArrayList]@()
    
    # Policies table
    # Index 0: Caption
    # Index 1: GUID
    # Index 2: Parent GUID
    # Index 3: ValueName
    # Index 4: ValueType
    # Index 5: ValueData
    # Index 6: Path
    # Index 7: Class
    $script:Policies = [System.Collections.ArrayList]@()
    
    # XML Documents
    $script:ADMXDoc = $null
    $script:ADMLDoc = $null
    
    # Patterns for default values (French and English)
    $script:DefaultValuePatterns = @(
        '@',                        # Standard format
        '(Default)',                # English exported format
        '(Valeur par défaut)',      # French exported format
        '(Valeur par d?faut)',      # French format with alternate encoding
        '(Standard)',               # German format
        '(Predeterminado)',         # Spanish format
        '(Predefinito)',            # Italian format
        '(Padrão)',                 # Portuguese format
        '(默认)',                    # Chinese format
        '(既定)',                    # Japanese format
        '(기본값)'                   # Korean format
    )
    
    #endregion
    
    #region Utility Functions
    
    function Write-Log {
        param([string]$Message)
        Write-Host $Message
    }
    
    function New-Guid {
        <#
        .SYNOPSIS
            Generates a GUID formatted for ADMX object names
        #>
        $guid = [System.Guid]::NewGuid().ToString()
        $guid = $guid.Replace("-", "_")
        return $guid
    }
    
    function Get-KeyName {
        <#
        .SYNOPSIS
            Extracts the key name from the full path
        #>
        param([string]$Path)
     
        $lastIndex = $Path.LastIndexOf("\")
        if ($lastIndex -gt 0) {
            return $Path.Substring($lastIndex + 1)
        }
        return "\\\\"
    }
    
    function Get-ParentGUID {
        <#
        .SYNOPSIS
            Finds the parent GUID based on path structure
        #>
        param(
            [string]$Path,
            [string]$Name
        )
     
        $findPath = $Path.Replace("\$Name", "")
        $result = "XML_2_ADMXL"
     
        foreach ($category in $script:Categories) {
            if ($findPath -eq $category.FullPath) {
                $result = $category.GUID
                break
            }
        }
     
        return $result
    }
    
    function Get-RegistryClass {
        <#
        .SYNOPSIS
            Determines the class (User/Machine/Both) and cleans the key path
        #>
        param([ref]$Key)
     
        $sKey = $Key.Value
        $tempClass = "Both"
     
        switch -Regex ($sKey) {
            "^HKEY_CURRENT_USER\\" {
                $sKey = $sKey.Substring(18)
                $tempClass = "User"
            }
            "^HKCU\\" {
                $sKey = $sKey.Substring(5)
                $tempClass = "User"
            }
            "^HKEY_LOCAL_MACHINE\\" {
                $sKey = $sKey.Substring(19)
                $tempClass = "Machine"
            }
            "^HKLM\\" {
                $sKey = $sKey.Substring(5)
                $tempClass = "Machine"
            }
            "^HKEY_CLASSES_ROOT" {
                $sKey = "SOFTWARE\Classes" + $sKey.Substring(17)
                $tempClass = "Both"
            }
            "^HKCR" {
                $sKey = "SOFTWARE\Classes" + $sKey.Substring(4)
                $tempClass = "Both"
            }
            "^HKEY_CURRENT_CONFIG" {
                $sKey = "SYSTEM\CurrentControlSet\Hardware Profiles\Current" + $sKey.Substring(19)
                $tempClass = "Machine"
            }
            "^HKCC" {
                $sKey = "SYSTEM\CurrentControlSet\Hardware Profiles\Current" + $sKey.Substring(4)
                $tempClass = "Machine"
            }
            "^HKEY_USERS\\" {
                $sKey = $sKey.Substring(11)
                $barIndex = $sKey.IndexOf("\")
                if ($barIndex -gt 0) {
                    $sKey = $sKey.Substring($barIndex + 1)
                }
                $tempClass = "Both"
            }
            "^HKU\\" {
                $sKey = $sKey.Substring(4)
                $barIndex = $sKey.IndexOf("\")
                if ($barIndex -gt 0) {
                    $sKey = $sKey.Substring($barIndex + 1)
                }
                $tempClass = "Both"
            }
        }
     
        $Key.Value = $sKey
        return $tempClass
    }
    
    function Test-IsDefaultValue {
        <#
        .SYNOPSIS
            Checks if the value name corresponds to a default value
        .DESCRIPTION
            Supports French, English and other language formats
        #>
        param([string]$ValueName)
     
        $trimmedName = $ValueName.Trim()
     
        # Check the @ format (standard default value)
        if ($trimmedName -eq '@') {
            return $true
        }
     
        # Check other default value patterns
        foreach ($pattern in $script:DefaultValuePatterns) {
            if ($trimmedName -eq $pattern -or $trimmedName -eq "`"$pattern`"") {
                return $true
            }
        }
     
        return $false
    }
    
    function Get-NormalizedDefaultValueName {
        <#
        .SYNOPSIS
            Normalizes the default value name for display
        #>
        param([string]$ValueName)
     
        if (Test-IsDefaultValue -ValueName $ValueName) {
            return "(Default)"
        }
     
        return $ValueName
    }
    
    function ConvertFrom-RegValueType {
        <#
        .SYNOPSIS
            Converts the value type from .reg format to a normalized format
        .DESCRIPTION
            Handles REG_SZ, REG_EXPAND_SZ, REG_DWORD, REG_QWORD, REG_BINARY, REG_MULTI_SZ
        #>
        param(
            [string]$RawType,
            [string]$RawData
        )
     
        $result = @{
            Type = "string"
            Data = $RawData
        }
     
        switch -Regex ($RawType.ToLower()) {
            # REG_SZ - Simple string (format: "value" without prefix)
            "^string$" {
                $result.Type = "string"
            }
            # REG_DWORD - 32-bit value (format: dword:00000001)
            "^dword$" {
                $result.Type = "dword"
                # Convert from hex to decimal if necessary
                if ($RawData -match "^[0-9a-fA-F]+$") {
                    try {
                        $result.Data = [Convert]::ToInt64($RawData, 16).ToString()
                    }
                    catch {
                        $result.Data = $RawData
                    }
                }
            }
            # REG_QWORD - 64-bit value (format: hex(b):01,00,00,00,00,00,00,00)
            "^hex\(b\)$" {
                $result.Type = "qword"
                # hex(b) data is in little-endian
                $result.Data = $RawData
            }
            # REG_EXPAND_SZ - Expandable string (format: hex(2):...)
            "^hex\(2\)$" {
                $result.Type = "expandsz"
                # Convert hex bytes to Unicode string
                $result.Data = Convert-HexToString -HexData $RawData
            }
            # REG_MULTI_SZ - Multi-string (format: hex(7):...)
            "^hex\(7\)$" {
                $result.Type = "multisz"
                # Convert hex bytes to multiple strings
                $result.Data = Convert-HexToMultiString -HexData $RawData
            }
            # REG_BINARY - Binary data (format: hex:...)
            "^hex$" {
                $result.Type = "binary"
            }
            # REG_NONE (format: hex(0):...)
            "^hex\(0\)$" {
                $result.Type = "none"
            }
            # Other unrecognized hex types
            "^hex\(\d+\)$" {
                $result.Type = "binary"
            }
            default {
                $result.Type = "string"
            }
        }
     
        return $result
    }
    
    function Convert-HexToString {
        <#
        .SYNOPSIS
            Converts a hex string (REG_EXPAND_SZ format) to Unicode text
        #>
        param([string]$HexData)
     
        try {
            # Clean the data
            $cleanHex = $HexData -replace '\s+', '' -replace ',', ''
      
            # Convert by character pairs
            $bytes = @()
            for ($i = 0; $i -lt $cleanHex.Length; $i += 2) {
                if ($i + 1 -lt $cleanHex.Length) {
                    $bytes += [Convert]::ToByte($cleanHex.Substring($i, 2), 16)
                }
            }
      
            # Decode to Unicode (UTF-16 LE)
            $result = [System.Text.Encoding]::Unicode.GetString($bytes)
            # Remove the final null character
            $result = $result.TrimEnd([char]0)
      
            return $result
        }
        catch {
            return $HexData
        }
    }
    
    function Convert-HexToMultiString {
        <#
        .SYNOPSIS
            Converts a hex string (REG_MULTI_SZ format) to text with separators
        #>
        param([string]$HexData)
     
        try {
            $str = Convert-HexToString -HexData $HexData
            # REG_MULTI_SZ uses null characters as separators
            $str = $str -replace "`0`0$", ""  # Remove the final double null
            $str = $str -replace "`0", "; "    # Replace nulls with separators
            return $str
        }
        catch {
            return $HexData
        }
    }
    
    #endregion
    
    #region REG File Import
    
    function Import-RegFile {
        <#
        .SYNOPSIS
            Reads and parses the .reg file to store data in internal tables
        #>
        param([string]$FilePath)
     
        # Initialize the root category (index 0)
        $rootCategory = [PSCustomObject]@{
            Name       = $DisplayName
            FullPath   = $DisplayName
            GUID       = "XML_2_ADMXL"
            ParentGUID = ""
        }
        [void]$script:Categories.Add($rootCategory)
     
        # Read the file with encoding detection
        $fileContent = $null
        $lines = @()
     
        # Try different encodings
        $encodings = @(
            [System.Text.Encoding]::Unicode,        # UTF-16 LE (Windows standard)
            [System.Text.Encoding]::UTF8,           # UTF-8
            [System.Text.Encoding]::Default,        # System default ANSI
            [System.Text.Encoding]::GetEncoding(1252)  # Windows-1252 (Western Europe)
        )
     
        foreach ($encoding in $encodings) {
            try {
                $lines = [System.IO.File]::ReadAllLines($FilePath, $encoding)
                if ($lines.Count -gt 0 -and ($lines[0] -match "Windows Registry|REGEDIT4")) {
                    Write-Log "File encoding detected: $($encoding.EncodingName)"
                    break
                }
            }
            catch {
                continue
            }
        }
     
        if ($lines.Count -eq 0) {
            Write-Error "Unable to read the file with any supported encoding."
            return
        }
     
        $currentSubKey = ""
        $i = 0
     
        while ($i -lt $lines.Count) {
            $line = $lines[$i]
      
            # Remove BOM and whitespace
            $line = $line.TrimStart([char]0xFEFF, [char]0xFFFE)
            $line = $line.Trim()
      
            # Ignore empty, declaratory or comment lines
            if ([string]::IsNullOrWhiteSpace($line) -or
                $line.StartsWith("Windows Registry") -or
                $line.StartsWith("REGEDIT4") -or
                $line.StartsWith(";")) {
                $i++
                continue
            }
      
            # Registry key [HKEY_...]
            if ($line.StartsWith("[") -and $line.EndsWith("]")) {
                $currentSubKey = $line.Substring(1, $line.Length - 2)
          
                # Ignore deletion keys [-HKEY_...]
                if (-not $currentSubKey.StartsWith("-")) {
                    $category = [PSCustomObject]@{
                        Name       = Get-KeyName -Path $currentSubKey
                        FullPath   = $currentSubKey
                        GUID       = New-Guid
                        ParentGUID = Get-ParentGUID -Path $currentSubKey -Name (Get-KeyName -Path $currentSubKey)
                    }
                    [void]$script:Categories.Add($category)
                }
                $i++
                continue
            }
      
            # Registry value
            # Supported formats:
            # @="value"                           - Default value
            # "(Default)"="value"                 - Default value (English)
            # "(Valeur par défaut)"="value"       - Default value (French)
            # "name"="value"                      - REG_SZ
            # "name"=dword:00000001               - REG_DWORD
            # "name"=hex:...                      - REG_BINARY
            # "name"=hex(2):...                   - REG_EXPAND_SZ
            # "name"=hex(7):...                   - REG_MULTI_SZ
            # "name"=hex(b):...                   - REG_QWORD
      
            $isDefaultValue = $false
            $valueName = ""
            $valueType = ""
            $valueData = ""
      
            # Check if it's a default value with @
            if ($line.StartsWith('@')) {
                $isDefaultValue = $true
                $valueName = "(Default)"
                $line = $line.Substring(1)  # Remove @
            }
            # Check if it's a value starting with "
            elseif ($line.StartsWith('"')) {
                $line = $line.Substring(1)  # Remove the first quote
          
                # Find the closing quote (handling escaped quotes)
                $quoteIndex = -1
                $j = 0
                while ($j -lt $line.Length) {
                    if ($line[$j] -eq '"' -and ($j -eq 0 -or $line[$j - 1] -ne '\')) {
                        $quoteIndex = $j
                        break
                    }
                    $j++
                }
          
                if ($quoteIndex -gt 0) {
                    $valueName = $line.Substring(0, $quoteIndex)
                    # Unescape quotes
                    $valueName = $valueName.Replace('\"', '"')
              
                    # Check if it's a localized default value
                    if (Test-IsDefaultValue -ValueName $valueName) {
                        $isDefaultValue = $true
                        $valueName = "(Default)"
                    }
              
                    $line = $line.Substring($quoteIndex + 1)
                }
                else {
                    $i++
                    continue
                }
            }
            else {
                $i++
                continue
            }
      
            # Find the = sign
            $equalIndex = $line.IndexOf('=')
            if ($equalIndex -lt 0) {
                $i++
                continue
            }
      
            $rightPart = $line.Substring($equalIndex + 1).Trim()
      
            # Handle value deletion (-)
            if ($rightPart -eq '-') {
                $i++
                continue
            }
      
            # Determine the type and value
            if ($rightPart.StartsWith('"')) {
                # REG_SZ - Simple string value
                $valueType = "string"
                $rightPart = $rightPart.Substring(1)
          
                # Find the closing quote
                $endQuote = -1
                $j = 0
                while ($j -lt $rightPart.Length) {
                    if ($rightPart[$j] -eq '"' -and ($j -eq 0 -or $rightPart[$j - 1] -ne '\')) {
                        $endQuote = $j
                        break
                    }
                    $j++
                }
          
                if ($endQuote -ge 0) {
                    $valueData = $rightPart.Substring(0, $endQuote)
                }
                else {
                    $valueData = $rightPart.TrimEnd('"')
                }
          
                # Unescape special characters
                $valueData = $valueData.Replace('\"', '"')
                $valueData = $valueData.Replace('\\', '\')
            }
            else {
                # Non-string value (dword, hex, etc.)
                $colonIndex = $rightPart.IndexOf(':')
                if ($colonIndex -gt 0) {
                    $valueType = $rightPart.Substring(0, $colonIndex).ToLower()
                    $valueData = $rightPart.Substring($colonIndex + 1)
              
                    # Handle multi-line hex values
                    if ($valueType.StartsWith("hex")) {
                        while ($valueData.TrimEnd().EndsWith('\')) {
                            $valueData = $valueData.TrimEnd().TrimEnd('\')
                            $i++
                            if ($i -lt $lines.Count) {
                                $nextLine = $lines[$i].Trim()
                                # Check that it's not a new entry
                                if (-not $nextLine.StartsWith('@') -and
                                    -not $nextLine.StartsWith('[') -and
                                    -not $nextLine.StartsWith('"') -and
                                    -not [string]::IsNullOrWhiteSpace($nextLine)) {
                                    $valueData += $nextLine
                                }
                                else {
                                    $i--
                                    break
                                }
                            }
                        }
                        $valueData = $valueData.Replace(" ", "")
                    }
                }
                else {
                    $i++
                    continue
                }
            }
      
            # Convert the value type
            $converted = ConvertFrom-RegValueType -RawType $valueType -RawData $valueData
      
            # Get the last added category
            if ($script:Categories.Count -gt 0) {
                $lastCategory = $script:Categories[$script:Categories.Count - 1]
                $keyPath = $lastCategory.FullPath
                $keyClass = Get-RegistryClass -Key ([ref]$keyPath)
          
                $policy = [PSCustomObject]@{
                    Caption    = $valueName
                    GUID       = New-Guid
                    ParentGUID = $lastCategory.GUID
                    ValueName  = $valueName
                    ValueType  = $converted.Type
                    ValueData  = $converted.Data
                    Path       = $keyPath
                    Class      = $keyClass
                    IsDefault  = $isDefaultValue
                }
                [void]$script:Policies.Add($policy)
          
                Write-Log "  Imported: [$($converted.Type)] $valueName = $($converted.Data.Substring(0, [Math]::Min(50, $converted.Data.Length)))$(if($converted.Data.Length -gt 50){'...'})"
            }
      
            $i++
        }
     
        Write-Log "Import completed: $($script:Categories.Count - 1) categories, $($script:Policies.Count) policies"
    }
    
    #endregion
    
    #region XML Document Creation
    
    function New-ADMXDocument {
        <#
        .SYNOPSIS
            Creates the ADMX template document
        #>
     
        $xml = New-Object System.Xml.XmlDocument
     
        # XML declaration
        $declaration = $xml.CreateXmlDeclaration("1.0", "utf-8", $null)
        [void]$xml.AppendChild($declaration)
     
        # Root element
        $root = $xml.CreateElement("policyDefinitions")
        $root.SetAttribute("revision", "1.0")
        $root.SetAttribute("schemaVersion", "1.0")
        $root.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema")
        $root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
        [void]$xml.AppendChild($root)
     
        # policyNamespaces
        $policyNamespaces = $xml.CreateElement("policyNamespaces")
        [void]$root.AppendChild($policyNamespaces)
     
        # target
        $target = $xml.CreateElement("target")
        $target.SetAttribute("prefix", "Call4cloud")
        $target.SetAttribute("namespace", "MSC.Policies." + (New-Guid))
        [void]$policyNamespaces.AppendChild($target)
     
        # using
        $using = $xml.CreateElement("using")
        $using.SetAttribute("prefix", "windows")
        $using.SetAttribute("namespace", "Microsoft.Policies.Windows")
        [void]$policyNamespaces.AppendChild($using)
     
        # supersededAdm
        $supersededAdm = $xml.CreateElement("supersededAdm")
        $supersededAdm.SetAttribute("fileName", "")
        [void]$root.AppendChild($supersededAdm)
     
        # resources
        $resources = $xml.CreateElement("resources")
        $resources.SetAttribute("minRequiredRevision", "1.0")
        $resources.SetAttribute("fallbackCulture", $Language)
        [void]$root.AppendChild($resources)
     
        # categories
        $categories = $xml.CreateElement("categories")
        [void]$root.AppendChild($categories)
     
        # policies
        $policies = $xml.CreateElement("policies")
        [void]$root.AppendChild($policies)
     
        return $xml
    }
    
    function New-ADMLDocument {
        <#
        .SYNOPSIS
            Creates the ADML template document
        #>
     
        $xml = New-Object System.Xml.XmlDocument
     
        # XML declaration
        $declaration = $xml.CreateXmlDeclaration("1.0", "utf-8", $null)
        [void]$xml.AppendChild($declaration)
     
        # Root element
        $root = $xml.CreateElement("policyDefinitionResources")
        $root.SetAttribute("revision", "1.0")
        $root.SetAttribute("schemaVersion", "1.0")
        $root.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema")
        $root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
        [void]$xml.AppendChild($root)
     
        # displayName
        $displayNameNode = $xml.CreateElement("displayName")
        $displayNameNode.InnerText = "REG_2_ADMXL"
        [void]$root.AppendChild($displayNameNode)
     
        # description
        $description = $xml.CreateElement("description")
        $description.InnerText = "This policy file was generated by the REG_2_ADMXL tool`nSource File: $RegFileName`n`nPlease change this part to match the description"
        [void]$root.AppendChild($description)
     
        # resources
        $resources = $xml.CreateElement("resources")
        [void]$root.AppendChild($resources)
     
        # stringTable
        $stringTable = $xml.CreateElement("stringTable")
        [void]$resources.AppendChild($stringTable)
     
        # presentationTable
        $presentationTable = $xml.CreateElement("presentationTable")
        [void]$resources.AppendChild($presentationTable)
     
        return $xml
    }
    
    #endregion
    
    #region Writing Categories and Policies
    
    function Write-Categories {
        <#
        .SYNOPSIS
            Converts registry keys to GPO categories
        #>
     
        $admxCategoriesNode = $script:ADMXDoc.SelectSingleNode("policyDefinitions/categories")
        $admlStringNode = $script:ADMLDoc.SelectSingleNode("policyDefinitionResources/resources/stringTable")
     
        foreach ($category in $script:Categories) {
            # Create the category node in ADMX
            $categoryNode = $script:ADMXDoc.CreateElement("category")
            $categoryNode.SetAttribute("name", "CAT_" + $category.GUID)
            $categoryNode.SetAttribute("displayName", "`$(string.CAT_" + $category.GUID + ")")
            $categoryNode.SetAttribute("explainText", "`$(string.CAT_" + $category.GUID + "_HELP)")
            [void]$admxCategoriesNode.AppendChild($categoryNode)
      
            # Add the parent category if it exists
            if (-not [string]::IsNullOrEmpty($category.ParentGUID)) {
                $parentCategory = $script:ADMXDoc.CreateElement("parentCategory")
                $parentCategory.SetAttribute("ref", "CAT_" + $category.ParentGUID)
                [void]$categoryNode.AppendChild($parentCategory)
            }
      
            # Create the strings in ADML
            $stringNode = $script:ADMLDoc.CreateElement("string")
            $stringNode.SetAttribute("id", "CAT_" + $category.GUID)
            $stringNode.InnerText = $category.Name
            [void]$admlStringNode.AppendChild($stringNode)
      
            $stringHelpNode = $script:ADMLDoc.CreateElement("string")
            $stringHelpNode.SetAttribute("id", "CAT_" + $category.GUID + "_HELP")
            $stringHelpNode.InnerText = "This Category configures the Values located under the [" + $category.FullPath + "] Key.`n`nThis policy file was generated by the REG_2_ADMXL tool`nPlease change this part to define the description"
            [void]$admlStringNode.AppendChild($stringHelpNode)
        }
    }
    
    function Write-Policies {
        <#
        .SYNOPSIS
            Writes the policies to the ADMX/ADML files
        #>
     
        $admxPoliciesNode = $script:ADMXDoc.SelectSingleNode("policyDefinitions/policies")
        $admlStringNode = $script:ADMLDoc.SelectSingleNode("policyDefinitionResources/resources/stringTable")
        $admlPresentationNode = $script:ADMLDoc.SelectSingleNode("policyDefinitionResources/resources/presentationTable")
     
        foreach ($policy in $script:Policies) {
            # Create the policy node in ADMX
            $policyNode = $script:ADMXDoc.CreateElement("policy")
            $policyNode.SetAttribute("name", "POL_" + $policy.GUID)
            $policyNode.SetAttribute("displayName", "`$(string.POL_" + $policy.GUID + ")")
            $policyNode.SetAttribute("explainText", "`$(string.POL_" + $policy.GUID + "_HELP)")
            $policyNode.SetAttribute("key", $policy.Path)
            $policyNode.SetAttribute("class", $policy.Class)
            $policyNode.SetAttribute("presentation", "`$(presentation.POL_" + $policy.GUID + ")")
            [void]$admxPoliciesNode.AppendChild($policyNode)
      
            # Parent category
            $parentCategory = $script:ADMXDoc.CreateElement("parentCategory")
            $parentCategory.SetAttribute("ref", "CAT_" + $policy.ParentGUID)
            [void]$policyNode.AppendChild($parentCategory)
      
            # supportedOn
            $supportedOn = $script:ADMXDoc.CreateElement("supportedOn")
            $supportedOn.SetAttribute("ref", "windows:SUPPORTED_WindowsVista")
            [void]$policyNode.AppendChild($supportedOn)
      
            # elements
            $elements = $script:ADMXDoc.CreateElement("elements")
            [void]$policyNode.AppendChild($elements)
      
            # Build the description with type indication
            $typeDescription = switch ($policy.ValueType) {
                "string" { "REG_SZ (String)" }
                "expandsz" { "REG_EXPAND_SZ (Expandable String)" }
                "multisz" { "REG_MULTI_SZ (Multi-String)" }
                "dword" { "REG_DWORD (32-bit Integer)" }
                "qword" { "REG_QWORD (64-bit Integer)" }
                "binary" { "REG_BINARY (Binary Data)" }
                default { $policy.ValueType.ToUpper() }
            }
      
            $defaultIndicator = ""
            if ($policy.IsDefault) {
                $defaultIndicator = " [DEFAULT VALUE]"
            }
      
            # Create the strings in ADML
            $stringNode = $script:ADMLDoc.CreateElement("string")
            $stringNode.SetAttribute("id", "POL_" + $policy.GUID)
            $stringNode.InnerText = $policy.Caption + $defaultIndicator
            [void]$admlStringNode.AppendChild($stringNode)
      
            $stringHelpNode = $script:ADMLDoc.CreateElement("string")
            $stringHelpNode.SetAttribute("id", "POL_" + $policy.GUID + "_HELP")
            $helpText = "This Policy configures the Value [" + $policy.ValueName + "] located under the [" + $policy.Path + "] Key.`n`n"
            $helpText += "Registry Value Type: $typeDescription`n"
            $helpText += "Original Value: [$($policy.ValueData)]`n`n"
            if ($policy.IsDefault) {
                $helpText += "Note: This is the DEFAULT value for this registry key.`n`n"
            }
            $helpText += "This policy file was generated by the REG_2_ADMXL tool`nPlease change this part to match your own description"
            $stringHelpNode.InnerText = $helpText
            [void]$admlStringNode.AppendChild($stringHelpNode)
      
            # Create the presentation in ADML
            $presentation = $script:ADMLDoc.CreateElement("presentation")
            $presentation.SetAttribute("id", "POL_" + $policy.GUID)
            [void]$admlPresentationNode.AppendChild($presentation)
      
            # Determine the valueName for the registry (@ for default)
            $registryValueName = $policy.ValueName
            if ($policy.IsDefault) {
                $registryValueName = ""  # Default value = empty name in ADMX
            }
      
            # Add the appropriate element based on type
            switch ($policy.ValueType.ToLower()) {
                "string" {
                    # Text element in ADMX
                    $textElement = $script:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "TXT_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($textElement)
              
                    # TextBox in ADML
                    $textBox = $script:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "TXT_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
              
                    $label = $script:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (REG_SZ)"
                    [void]$textBox.AppendChild($label)
              
                    $defaultValue = $script:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
                "expandsz" {
                    # REG_EXPAND_SZ - Treated as text with indication
                    $textElement = $script:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "EXP_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    $textElement.SetAttribute("expandable", "true")
                    [void]$elements.AppendChild($textElement)
              
                    # TextBox in ADML
                    $textBox = $script:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "EXP_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
              
                    $label = $script:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (REG_EXPAND_SZ - supports %variables%)"
                    [void]$textBox.AppendChild($label)
              
                    $defaultValue = $script:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
                "multisz" {
                    # REG_MULTI_SZ - String list
                    $multiTextElement = $script:ADMXDoc.CreateElement("multiText")
                    $multiTextElement.SetAttribute("id", "MLT_" + $policy.GUID)
                    $multiTextElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($multiTextElement)
              
                    # MultiTextBox in ADML
                    $multiTextBox = $script:ADMLDoc.CreateElement("multiTextBox")
                    $multiTextBox.SetAttribute("refId", "MLT_" + $policy.GUID)
                    $multiTextBox.InnerText = $policy.ValueName + " (REG_MULTI_SZ - one entry per line)"
                    [void]$presentation.AppendChild($multiTextBox)
                }
                "dword" {
                    # Decimal element in ADMX
                    $decimalElement = $script:ADMXDoc.CreateElement("decimal")
                    $decimalElement.SetAttribute("id", "DXT_" + $policy.GUID)
                    $decimalElement.SetAttribute("valueName", $registryValueName)
                    $decimalElement.SetAttribute("minValue", "0")
                    $decimalElement.SetAttribute("maxValue", "4294967295")
                    [void]$elements.AppendChild($decimalElement)
              
                    # DecimalTextBox in ADML
                    $decimalTextBox = $script:ADMLDoc.CreateElement("decimalTextBox")
                    $decimalTextBox.SetAttribute("refId", "DXT_" + $policy.GUID)
              
                    # Add the default value if it's a valid number
                    if ($policy.ValueData -match '^\d+$') {
                        $decimalTextBox.SetAttribute("defaultValue", $policy.ValueData)
                    }
              
                    $decimalTextBox.InnerText = $policy.ValueName + " (REG_DWORD)"
                    [void]$presentation.AppendChild($decimalTextBox)
                }
                "qword" {
                    # REG_QWORD - Treated as text because ADMX doesn't natively support 64-bit
                    $textElement = $script:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "QXT_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($textElement)
              
                    # TextBox in ADML with note
                    $textBox = $script:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "QXT_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
              
                    $label = $script:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (REG_QWORD - enter as hex bytes)"
                    [void]$textBox.AppendChild($label)
              
                    $defaultValue = $script:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
                { $_ -in "binary", "hex" } {
                    # REG_BINARY - Hex treated as text
                    $textElement = $script:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "HXT_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($textElement)
              
                    # TextBox in ADML
                    $textBox = $script:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "HXT_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
              
                    $label = $script:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (REG_BINARY - hex format: 00,01,02,...)"
                    [void]$textBox.AppendChild($label)
              
                    $defaultValue = $script:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
                default {
                    Write-Log "Note: Unknown datatype '$($policy.ValueType)' for value '$($policy.ValueName)', treating as string"
              
                    $textElement = $script:ADMXDoc.CreateElement("text")
                    $textElement.SetAttribute("id", "UNK_" + $policy.GUID)
                    $textElement.SetAttribute("valueName", $registryValueName)
                    [void]$elements.AppendChild($textElement)
              
                    $textBox = $script:ADMLDoc.CreateElement("textBox")
                    $textBox.SetAttribute("refId", "UNK_" + $policy.GUID)
                    [void]$presentation.AppendChild($textBox)
              
                    $label = $script:ADMLDoc.CreateElement("label")
                    $label.InnerText = $policy.ValueName + " (Unknown type: " + $policy.ValueType + ")"
                    [void]$textBox.AppendChild($label)
              
                    $defaultValue = $script:ADMLDoc.CreateElement("defaultValue")
                    $defaultValue.InnerText = $policy.ValueData
                    [void]$textBox.AppendChild($defaultValue)
                }
            }
        }
    }
    
    #endregion
    
    #region Saving XML Files
    
    function Save-XMLFiles {
        <#
        .SYNOPSIS
            Saves the ADMX and ADML files to the appropriate locations
        #>
     
        # Use a case-insensitive regular expression to replace the extension
        $admxName = $RegFileName -replace "\.reg$", ".admx"
        if ($admxName -eq $RegFileName) {
            # If the replacement didn't work (uppercase extension)
            $admxName = $RegFileName -replace "\.REG$", ".admx"
        }
     
        $admlName = $admxName -replace "\.admx$", ".adml"
     
        # Determine the folder for the ADML
        $parentFolder = Split-Path -Parent $admlName
        $fileName = Split-Path -Leaf $admlName
     
        if ([string]::IsNullOrEmpty($parentFolder)) {
            $parentFolder = Get-Location
            $admxName = Join-Path $parentFolder (Split-Path -Leaf $admxName)
        }
     
        $langFolder = Join-Path $parentFolder $Language
        $admlName = Join-Path $langFolder $fileName
     
        # Create the language folder if necessary
        if (-not (Test-Path $langFolder)) {
            New-Item -ItemType Directory -Path $langFolder -Force | Out-Null
            Write-Log "Created language folder: $langFolder"
        }
     
        # Configure XML save settings
        $xmlWriterSettings = New-Object System.Xml.XmlWriterSettings
        $xmlWriterSettings.Indent = $true
        $xmlWriterSettings.IndentChars = "  "
        $xmlWriterSettings.Encoding = [System.Text.Encoding]::UTF8
     
        # Save ADMX
        $xmlWriter = [System.Xml.XmlWriter]::Create($admxName, $xmlWriterSettings)
        $script:ADMXDoc.Save($xmlWriter)
        $xmlWriter.Close()
     
        # Save ADML
        $xmlWriter = [System.Xml.XmlWriter]::Create($admlName, $xmlWriterSettings)
        $script:ADMLDoc.Save($xmlWriter)
        $xmlWriter.Close()
     
        Write-Log ""
        Write-Log "Files saved successfully:"
        Write-Log "  ADMX: $admxName"
        Write-Log "  ADML: $admlName"
    }
    
    #endregion
    
    #region Main
    
    # Banner
    Write-Log "=============================================="
    Write-Log "  REG_2_ADMXL - Registry to ADMX/ADML Converter"
    Write-Log "  PowerShell 5.1 Version"
    Write-Log "=============================================="
    Write-Log ""
    
    # Check that the file exists
    if (-not (Test-Path $RegFileName)) {
        Write-Error "File not found. Unable to open $RegFileName."
        exit 1
    }
    
    $RegFileName = (Resolve-Path $RegFileName).Path
    Write-Log "Input file: $RegFileName"
    Write-Log "Language: $Language"
    Write-Log "Display Name: $DisplayName"
    Write-Log ""
    
    # Import the REG file
    Write-Log "Importing registry file..."
    Import-RegFile -FilePath $RegFileName
    
    if ($script:Policies.Count -eq 0) {
        Write-Warning "No policies were imported from the registry file."
        Write-Warning "Please check if the file format is correct."
        exit 1
    }
    
    # Create the ADMX and ADML documents
    Write-Log ""
    Write-Log "Creating ADMX/ADML documents..."
    $script:ADMXDoc = New-ADMXDocument
    $script:ADMLDoc = New-ADMLDocument
    
    # Write the categories and policies
    Write-Log "Writing categories and policies..."
    Write-Categories
    Write-Policies
    
    # Save the files
    Save-XMLFiles
    
    Write-Log ""
    Write-Log "=============================================="
    Write-Log "  Conversion completed successfully!"
    Write-Log "=============================================="
    
    #endregion

    Code:
    Fixed default values with blank data. For example, ADMX files were not generated when a REG_SZ value had an empty default value. This is now fixed.
    
    Other changes:
    
     The same script can now be used from the command line or without parameters in graphical mode.
    

    [​IMG]
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  2. Tiger-1

    Tiger-1 MDL Guru

    Oct 18, 2014
    8,386
    11,802
    270
    thanks a lot friend
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  3. pm67310

    pm67310 MDL Guru

    Sep 6, 2011
    3,947
    3,242
    120
    #3 pm67310, Feb 19, 2026
    Last edited: Sep 25, 2026
    (OP)
    Reserved
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  4. pm67310

    pm67310 MDL Guru

    Sep 6, 2011
    3,947
    3,242
    120
    #4 pm67310, Sep 25, 2026
    Last edited: Sep 25, 2026
    (OP)
    added V2
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...