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: 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. 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. Spoiler: REG_2_ADMX - V2 / save as REG_2_ADMXL.ps1 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 Spoiler: reg2admx.ps1 - old version 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 Spoiler: CHANGE IN V2 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.