Thursday, October 7, 2010

GPO Scripts – Creating your own ADMX/ADML files


Unfortunately a full decade after the release of Active Directory and the introduction of its powerful Group Policy Engine, many, if not most, application vendors have not yet got with the program and produced template files for managing their applications via GPO. To get around this limitation, many administrators have gotten in the habit of "rolling their own" policy template files in order to be able to manage application settings quickly and easily. Unfortunately, doing so manually requires a lot of effort in order to get the file formatting correct.
Luckily the author or today's script from the Technet Script Center, Mariano S. Cosentino, has made our life much easier by being able to simply convert .REG registry files to appropriate ADMx/ADML files automatically. While not perfect, the script definitely takes most of the work out of the process. To do this the script makes a few assumptions:
  1. the "name" of the value is also used as the caption for all displays.
  2. all dwords values are assigned a numeric textbox for data entry
  3. all other value types are treated as strings and assigned a textbox for data entry.


Known Limitations
@ or (Default)
The tools will not handle correctly the "@" or unnamed value name. This is the one that in the registry editor shows as (Default).
WORKAROUND: For now it's assigning the value to a "(Default)" value, but as you can see in the examples bellow, windows does not recognize this "(Default)" value as the real "(Default)" value.


Hex, Hex(0) ...
This is another example of things that I was not able to learn from the ADMX files that I have available.
We have several cases of registry files that assigning a value composed of several 2 char Hexadecimal values, but I have not find any ADMX file that applies this kind of settings to to policies.
WORKAROUND: For now, the script will make this hexadecimal values into a text.


HKU or HKEY_USERS
The ADMX definition allows you to set policies for Users (Current User, actually) and/or Computers, this does not include the HKU or the HKEY_USERS.
WORKAROUND: The script will treat any HKU policy as a HKCU (it will clean any named user defined as part of the HKU).


Usage:


CSCRIPT REG_2_ADMXL.vbs registry-file language [name]


registry-file is the name and path of the registry file to be converted.
language is the language and culture to be used, ie: en-US, sp-AR, etc.
name Display Name to show in the GPO. if omited "REG_2_ADMXL Generated Policy" will be used.
The output file will be named after the .REG file (if the input is myfile.REG, the output will be myfile.ADMX and myfile.ADML.
The ADMX output file will be saved in the same folder the input .REG file is located, while the ADML output file will be saved in a subfolder of the one the .REG file is located. The subfolder will be named after the language specified.
So, if the reg file is C:\myapp\myfile.reg and the lang is en-US, then the ADMX file will be as in C:\myAPP\myfile.ADMX and the ADML file will be saved as C:\myAPP\en-US\myfile.ADMX


' The goal of this tools is to read a .reg file and generate a .admx that would allow us to set those settings thru GPO


' +-----------------------------------------------------------------------------+
' | App.Name :    REG_2_ADMXL.vbs                             |
' | App.Description :     |
' | This tools reads a .reg file and generates a ADMX/ADML |
' | pair of files that would allow us to set those settings thru GPO |
' | This file accepts 3 parameters:                         |
' |     1) Reg File to convert                                |
' |                            2) Default Language (i.e.: en-US or sp-AR, po-BR) |
' |                            3) (optional) Display Name to show in the GPO                                                    |
' |                                                                                |
' | The output file will be named after the .REG file (if    |
' | the input is myfile.REG, the output will be myfile.ADMX and myfile.ADML)    |
' |     The ADMX output file will be saved in the same folder |
' | the input .REG file is located                                                |
' |     The ADML output file will be saved in a subfolder of |
' | the one the .REG file is located. The subfolder will be named after the     |
' |    Language specified.                                                            |
' |                        So, if the reg file is C:\myapp\myfile.reg and the lang |
' | is en-US, then the ADMX file will be as in C:\myAPP\myfile.ADMX and the |
' | ADML file will be saved as C:\myAPP\en-US\myfile.ADMX                        |
' |                                                                                |
' |                                                                                |
' |                                                                                |
' |     This file does a very simple assignment of input fields |
' |    If the data type is a dword a numeric textbox is used, otherwise a textbox |
' | will be used.                                 |
' |     In my experience, this is good enought for 90% of the    |
' | cases. And if you would like fancier stuff (like comboboxes, listboxes, |
' | date picker, etc.) you can still use this tool to generate the initial file |
' | and then add the stuff you need.                 |
' |                                                                                |
' |                                                                                |
' | Current Version:    1.02                                                    |
' |          By:    Mcosentino    (reg_2_ADMXL at marianok.com.ar)                    |
' |      Date:        Mar. 01 2010                                        |
' |                                                                                |
' +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+
' | 1.02 03/01/10    mcosentino    Fixed issue with HKCU/HKCC/HKCR/HKC not                |
' |                     being parsed correctly    |
' |                    Thanks to JimmyRolaff for reporting it    |
' |                                        |
' +-----------------------------------------------------------------------------+
' |                                                                                |
' | How to use it:                                                                 |
' |     cscript REG_2_ADMXL.vbs <Registry file> <Language> [<name>] |
' |        Sample:                                                                    |
' |     cscript REG_2_ADMXL.vbs c:\myapp\myfile.reg en-US "MY APP Policies" |
' |                                                                                |
' +-----------------------------------------------------------------------------+




Const ForReading = 1


dim sRegFileName
dim sLang
dim sRootDisplay
dim objFSO
set objFSO = createobject("Scripting.FileSystemObject")




' check that we have the necesary arguments, if not display instructions and end.


if wscript.Arguments.Count < 2 then
wscript.echo "Missing Parameters:" & vbcrlf & _
"Usage:" & vbcrlf & vbtab & _
"cscript " & WScript.ScriptName & " <Registry file> <Language> [<name>]" & vbcrlf & _
" Sample:" & vbcrlf & vbtab & _
"cscript " & WScript.ScriptName & " c:\myapp\myfile.reg en-US MY_APP_Policies"
    wscript.quit
else
sRegFileName=Wscript.Arguments.Item(0)
sLang=Wscript.Arguments.Item(1)
end if






' Let's check that the input file really exists


if not objFSO.FileExists(sRegFileName) then
wscript.echo "File not found. Unable to open " & sRegFileName & "."
    wscript.quit
end if




' OK, we have the necesary parameters, let's the games begin






' Let's check of they have specified a root node, otherwise I will use mine
if wscript.Arguments.Count >= 3 then
sRootDisplay=Wscript.Arguments.Item(2)
else
sRootDisplay="REG_2_ADMXL Generated Policy"
end if




' Define the Table that will hold the categories.
dim lCategories()
redim lCategories(4,0)
' First field is the name
' Second field is the data
' Third field is the GUID
' Fourth field is the GUID for the parent


    ' Set Node 0 with the data of the Root node.
    ' I must admit that is not really nice to be using node 0 this way, but it works.
     lCategories(4,0) = ""
     lCategories(1,0) = sRootDisplay
     lCategories(2,0) = sRootDisplay
     lCategories(3,0) = "MARIANOKS_XML_2_ADMXL"




' Define the table that will hold the value assignments (from now on "policies")
dim lPolicies()
redim lPolicies(8,0)
' First field is the caption
' Second field is the GUID
' Third field is the GUID for the parent
' Fourth field is the valueName
' Fifth field is the ValueType
' Sixth field is the ValueData
' Seventh field is the PATH
' Eight Field is the class (user Machine, both)




' Read and import the registry file.
ImportRegFile(sRegFileName)




'Create the Basic ADMX/ADML files
set ADMXDoc = CreateADMX
set ADMLDoc = CreateADML


'ListCategories
'ListPolicies


'Generate the XML
WriteCategories
WritePolicies


' Write the XML files to disk
SaveXML












' ------------------------------------------------------------------------------------------
' ALL FUNCTIONS START HERE
' ------------------------------------------------------------------------------------------


sub SaveXML()
    ' This function Saves the XML files in the correct locations and with the correct name


dim ADMXName, ADMLName, iLastBar
ADMXName = replace(lcase(sRegFileName),".reg",".ADMX")
ADMLName = replace(lcase(sRegFileName),".reg",".ADML")
iLastBar = InStrRev(ADMLName,"\")
if iLastBar=0 then
ADMLName = sLang & "\" & ADMLName
strFolder = sLang
elseif iLastBar=1 then
ADMLName = "\" & sLang & ADMLName
strFolder = "\" & sLang
else
strFolder = left(ADMLName,iLastBar) & sLang
ADMLName = left(ADMLName,iLastBar) & sLang & "\" & right(ADMLName,len(ADMLName) - iLastBar)
end if


ADMXDoc.save ADMXName
if not objFSO.FolderExists(strFolder) then
objFSO.CreateFolder(strFolder)
end if
ADMLDoc.save ADMLName
end sub




function CreateADMX ()


    ' This function creates the a template ADMX
    ' The ADMX file is the one that contains the policies, the ADML file(s) is the one that contains all the strings and tex, and is where all language/culture customization takes place.
    ' In other words, if you want to have the same policy file in a diferent language, you copy the ADML to another folder and trasnlate each string (preserving the rest of the file)
    ' All comments and settings are updated with the information from the .REG file
    ' Most of this values were obtained from reading existing ADMX files, I'm unsure of the need for some of them, so I'm including them to be safe


' Create the xml Document
Set xmlDoc = CreateObject("MSXML.DOMDocument")


' Create the root node for the document
Set objRoot = xmlDoc.createElement("policyDefinitions")
xmlDoc.appendChild objRoot


' Set the properties for the root node as required for the ADMX/L to work correctly
Set xmlAttribute = xmlDoc.createAttribute("revision")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("1.0"))
    objRoot.Attributes.setNamedItem(xmlAttribute)


Set xmlAttribute = xmlDoc.createAttribute("schemaVersion")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("1.0"))
     objRoot.Attributes.setNamedItem(xmlAttribute)


Set xmlAttribute = xmlDoc.createAttribute("xmlns:xsd")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("http://www.w3.org/2001/XMLSchema"))
     objRoot.Attributes.setNamedItem(xmlAttribute)


Set xmlAttribute = xmlDoc.createAttribute("xmlns:xsi")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("http://www.w3.org/2001/XMLSchema-instance"))
     objRoot.Attributes.setNamedItem(xmlAttribute)
    
    'Set xmlAttribute = xmlDoc.createAttribute("xmlns")
    ''Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("http://www.microsoft.com/GroupPolicy/PolicyDefinitions"))
    'Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions"))
    'objRoot.Attributes.setNamedItem(xmlAttribute)




'Create the policynamspaces node for future use
Set objPolNS = xmlDoc.createElement("policyNamespaces")
objRoot.appendChild objPolNS


'Create the Target node (Looks like we need a unique one for each app, so I'll be using a GUID to ensure this)
Set tNode = xmlDoc.createElement("target")
objPolNS.appendChild tNode
' Set the properties for the target node
Set xmlAttribute = xmlDoc.createAttribute("prefix")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("Marianok")) ' I needed a unique Namespace, so I used mine (I deserve a little credit for my work, after all)
     tNode.Attributes.setNamedItem(xmlAttribute)
    

Set xmlAttribute = xmlDoc.createAttribute("namespace")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("MSC.Policies." & GenerateGUID ))
     tNode.Attributes.setNamedItem(xmlAttribute)




'Create the using node (defines this a a GPO)
Set tNode = xmlDoc.createElement("using")
objPolNS.appendChild tNode
' Set the properties for the target node
Set xmlAttribute = xmlDoc.createAttribute("prefix")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("windows"))
     tNode.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = xmlDoc.createAttribute("namespace")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("Microsoft.Policies.Windows"))
     tNode.Attributes.setNamedItem(xmlAttribute)


'Create the supersededAdm node (not sure i need this, but maybe some one will use it if they find it on the output file)
Set tNode = xmlDoc.createElement("supersededAdm")
objRoot.appendChild tNode
' Set the properties for the supersededAdm node
Set xmlAttribute = xmlDoc.createAttribute("fileName")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode(""))
     tNode.Attributes.setNamedItem(xmlAttribute)


'Create the resources node (not sure i need this)
Set tNode = xmlDoc.createElement("resources")
objRoot.appendChild tNode
' Set the properties for the target node
Set xmlAttribute = xmlDoc.createAttribute("minRequiredRevision")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("1.0"))
     tNode.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = xmlDoc.createAttribute("fallbackCulture")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode(sLang))
     tNode.Attributes.setNamedItem(xmlAttribute)


' ' Create the definitions node
' Set tNode = xmlDoc.createElement("definitions")
' objRoot.appendChild tNode




' Create the categories node, All categories will end up here
Set tNode = xmlDoc.createElement("categories")
objRoot.appendChild tNode


' Create the policies node, All Policies will end up here
Set tNode = xmlDoc.createElement("policies")
objRoot.appendChild tNode








'<?xml version="1.0" encoding="utf-8"?>


' Set objIntro = xmlDoc.createProcessingInstruction ("xml","version='1.0'")
' xmlDoc.insertBefore objIntro,xmlDoc.childNodes(0)






set CreateADMX = xmlDoc


end function








function CreateADML ()


    ' This function creates the a template ADML
    ' The ADMX file is the one that contains the policies, the ADML file(s) is the one that contains all the strings and tex, and is where all language/culture customization takes place.
    ' In other words, if you want to have the same policy file in a diferent language, you copy the ADML to another folder and trasnlate each string (preserving the rest of the file)
    ' All comments and settings are updated with the information from the .REG file
    ' Most of this values were obtained from reading existing ADMX/ADML files, I'm unsure of the need for some of them, so I'm including them to be safe




' Create the xml
Set xmlDoc = CreateObject("MSXML.DOMDocument")


' Create the root node
Set objRoot = xmlDoc.createElement("policyDefinitionResources")
xmlDoc.appendChild objRoot


' Set the properties for the root node
Set xmlAttribute = xmlDoc.createAttribute("revision")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("1.0"))
    objRoot.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = xmlDoc.createAttribute("schemaVersion")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("1.0"))
     objRoot.Attributes.setNamedItem(xmlAttribute)






Set xmlAttribute = xmlDoc.createAttribute("xmlns:xsd")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("http://www.w3.org/2001/XMLSchema"))
     objRoot.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = xmlDoc.createAttribute("xmlns:xsi")
Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("http://www.w3.org/2001/XMLSchema-instance"))
     objRoot.Attributes.setNamedItem(xmlAttribute)
' Set xmlAttribute = xmlDoc.createAttribute("xmlns")
' Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("http://www.microsoft.com/GroupPolicy/PolicyDefinitions"))
'' Set xmlText = xmlAttribute.appendChild(xmlDoc.createTextNode("http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions"))
'     objRoot.Attributes.setNamedItem(xmlAttribute)






'Create the displayName node
Set objPolNS = xmlDoc.createElement("displayName")
objRoot.appendChild objPolNS
objPolNS.text="REG_2_ADMXL"


'Create the description node
Set objPolNS = xmlDoc.createElement("description")
objRoot.appendChild objPolNS
objPolNS.text="This policy file was generated by the REG_2_ADMXL tool" & vbcrlf & _
"Source File: " & sRegFileName & vbcrlf & vbcrlf & _
"This Freeware Tool can be downloaded from www.marianok.com.ar"


'Create the resources node
Set objPolNS = xmlDoc.createElement("resources")
objRoot.appendChild objPolNS


'Create the stringTable node. This will hold all strings for the language/culture
Set tNode = xmlDoc.createElement("stringTable")
objPolNS.appendChild tNode


'Create the presentationTable node. this will hold the diferent ways in with we want the data show (texbox, combobox, listbox, calendar picker, etc)
Set tNode = xmlDoc.createElement("presentationTable")
objPolNS.appendChild tNode


Set objIntro = xmlDoc.createProcessingInstruction ("xml","version='1.0'")
xmlDoc.insertBefore objIntro,xmlDoc.childNodes(0)






set CreateADML = xmlDoc


end function








Sub ImportRegFile(sRegFileName)


    ' this function will read the .REG file and parse it in order to store it's data in the internal tables
    
    dim strLine, sTempLine, sSubkey, sValueName, sValuetype, SValueData, iIndex ,sTestFile
    Set objTextFile = objFSO.OpenTextFile(sRegFileName , ForReading,, -2)


' if objTextFile.AtEndOfStream <> True then sTempLine = objtextFile.ReadLine
    Do While objTextFile.AtEndOfStream <> True
if (objTextFile.AtEndOfStream <> True) and (bSkip <> true) then sTempLine = objtextFile.ReadLine
bSkip = false


        strLine = sTempLine
        If left(strLine, 16) = "Windows Registry" or strLine = "" or left(strLine, 8) = "REGEDIT4" Then
            ' If it's a declaratory line, then skip        
        else
            ' If this is a KEY, then create a new category
            If left(strLine, 1) = ";" Then
     ' If it's a comment line, then skip        
            elseIf left(strLine, 1) = "[" Then
            ' If this is a KEY, then create a new category
                sSubkey = left(right(strLine, len(strLine)- 1), len(strLine)- 2)
                redim preserve lCategories(4,ubound(lCategories,2) +1)        
lCategories(1,ubound(lCategories,2)) = GetName(sSubkey)
lCategories(2,ubound(lCategories,2)) = sSubkey
                lCategories(3,ubound(lCategories,2)) = GenerateGUID
lCategories(4,ubound(lCategories,2)) = GetParentGUID(sSubkey,lCategories(1,ubound(lCategories,2)))


            elseif left(strLine, 1) = """" or left(strLine, 1) = "@" Then


                'If the line starts with @, it's an asignation of the Default Value.
                ' I'm forcing the string to be the corresponding valuename
                if left(strLine, 1) = "@" Then
                strLine = """(Default)""" & right(strLine, len(strLine)- 1)
'                 strLine = """@""" & right(strLine, len(strLine)- 1)
end if            
                'If the line starts with double quotes, it's an asignation of values, this will be converted into a pilicy for the ADMX/L files
                strLine = right(strLine, len(strLine)- 1)
                iIndex = instr(strLine,"""")
                if iIndex > 0 then
                    sValueName= left(strline,iIndex-1)
redim preserve lPolicies(8, ubound(lPolicies,2) +1)        
                 Set TypeLib = CreateObject("Scriptlet.TypeLib")
lPolicies(1,ubound(lPolicies,2)) = sValueName
                 lPolicies(2,ubound(lPolicies,2)) = GenerateGUID
lPolicies(3,ubound(lPolicies,2)) = lCategories(3,ubound(lCategories,2))
lPolicies(4,ubound(lPolicies,2)) = sValueName


strLine=trim(strLine)
                iIndex2 = instr(strLine,"=")
                    strLine = right(strLine, len(strLine)- iIndex2)
strLine=trim(strLine)
if left(strLine,1) = " " then strLine=right(strLine, len(strLine)-1)
strLine=trim(strLine)
if left(strLine,1) = chr(9) then strLine=right(strLine, len(strLine)-1)
strLine=trim(strLine)
if left(strLine,1) = " " then strLine=right(strLine, len(strLine)-1)
strLine=trim(strLine)
if left(strLine,1) = chr(9) then strLine=right(strLine, len(strLine)-1)




                    ' Now, we need to determine if what we have on the right side of the = is an string or another kind of value.
                    if left(strline,1)="""" then
                        ' String value
                        sValuetype = "string"
                        SValueData = left(right(strLine, len(strLine)- 1), len(strLine)- 2)
                    else
                        ' non string
                        iIndex = instr(strLine,":")
                        if iIndex > 0 then
                            sValuetype = left(strline,iIndex-1)
                            SValueData = right(strLine, len(strLine)- iIndex)
                          

if left(lcase(sValuetype),3) = "hex" then
bSkip = false
do
if right(SValueData,1)="\" then
SValueData = left(SValueData,len(SValueData)-1)
if (objTextFile.AtEndOfStream <> True) and (bSkip <> true) then
sTempLine = objtextFile.ReadLine
if left(sTempLine,1) = "@" or left(sTempLine,1) = "[" or left(sTempLine,1) = """" then
bSkip = true
exit do
else
SValueData = SValueData & sTempLine
end if
else
'bSkip = true
exit do
end if
else
'bSkip = true
exit do
end if
loop until bSkip
SValueData = replace (SValueData," ","")
end if
                        else
'                            sWriteLog "Invalid record (datatype w/o ':'): " & sTempLine            
                        end if
                    end if
     lPolicies(5,ubound(lPolicies,2)) = sValuetype
     lPolicies(6,ubound(lPolicies,2)) = replace(SValueData,"\\","\")
sText=lCategories(2,ubound(lCategories,2))
                lPolicies(8,ubound(lPolicies,2)) = GetClass(sText)
                lPolicies(7,ubound(lPolicies,2)) = sText








                else
                    sWriteLog "Invalid record (Valuename w/o end quote): " & sTempLine                    
                end if
            else
                sWriteLog "Invalid record (or Valuename w/o quote): " & sTempLine                    
            end if
        end if
' if (objTextFile.AtEndOfStream <> True) and (bSkip <> true) then sTempLine = objtextFile.ReadLine
' bSkip = false
    Loop
    objtextFile.Close


end sub






sub sWriteLog (stexto)
wscript.echo stexto
end sub








sub ListCategories()


    ' This is a function for testing porpouses, it will just list all items stored on the Categories table
dim iCounter


for icounter = 1 to ubound(lCategories,2)
sWriteLog "Caption: [" & lCategories(1,iCounter) & "] " & vbcrlf & vbtab & "Path: [" & lCategories(2,iCounter) & "] " & vbcrlf & vbtab & " GUID: [" & lCategories(3,iCounter) & "] " & vbcrlf & vbtab & " ParentGuid: [" & lCategories(4,iCounter) & "]"
next
end sub




sub ListPolicies()


    ' This is a function for testing porpouses, it will just list all items stored on the Policies table


dim iCounter


for icounter = 1 to ubound(lPolicies,2)
sWriteLog "Caption: [" & lPolicies(1,iCounter) & "] " & vbcrlf & vbtab & " GUID: [" & lPolicies(2,iCounter) & "] " & vbcrlf & vbtab & " ParentGuid: [" & lPolicies(3,iCounter) & "] " & vbcrlf & vbtab & " Name: [" & lPolicies(3,iCounter) & "] " & vbcrlf & vbtab & " Type: [" & lPolicies(3,iCounter) & "] " & vbcrlf & vbtab & " Data: [" & lPolicies(6,iCounter) & "]"
next
end sub








sub WriteCategories()


    'This function converts the KEYS read from the .REG file into the corresponding categories for the GPOs


dim iCounter
set ADMXParentNode = ADMXDoc.selectSingleNode("policyDefinitions/categories")
set ADMLStringNode = ADMLDoc.selectSingleNode("policyDefinitionResources/resources/stringTable")
for icounter = 0 to ubound(lCategories,2)


' sWriteLog "Path: [" & lCategories(1,iCounter) & "] " & vbcrlf & vbtab & _
' " GUID: [" & lCategories(2,iCounter) & "] " & vbcrlf & vbtab & _
' " ParentGuid: [" & lCategories(3,iCounter) & "]"




' Create the category node on the ADMX file
Set objCategory = ADMXDoc.createElement("category")
ADMXParentNode.appendChild objCategory


' Set the properties for the category node
Set xmlAttribute = ADMXDoc.createAttribute("name")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("CAT_" & lCategories(3,iCounter)))
     objCategory.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("displayName")
sTexto="$(string.CAT_" & lCategories(3,iCounter)
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode(sTexto & ")"))
     objCategory.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("explainText")
sTexto="$(string.CAT_" & lCategories(3,iCounter)
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode(sTexto & "_HELP)"))
     objCategory.Attributes.setNamedItem(xmlAttribute)


    ' Set the parent category (so AD knows how to build the Tree)
if lCategories(4,iCounter) <> "" then
Set objTemp = ADMXDoc.createElement("parentCategory")
objCategory.appendChild objTemp
Set xmlAttribute = ADMXDoc.createAttribute("ref")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("CAT_" & lCategories(4,iCounter)))
objTemp.Attributes.setNamedItem(xmlAttribute)
end if




' Create the string node on the ADML file
Set objTemp = ADMLDoc.createElement("string")
ADMLStringNode.appendChild objTemp
Set xmlAttribute = ADMLDoc.createAttribute("id")
Set xmlText = xmlAttribute.appendChild(ADMLDoc.createTextNode("CAT_" & lCategories(3,iCounter)))
objTemp.Attributes.setNamedItem(xmlAttribute)
objTemp.text = lCategories(1,iCounter)
Set objTemp = ADMLDoc.createElement("string")
ADMLStringNode.appendChild objTemp
Set xmlAttribute = ADMLDoc.createAttribute("id")
Set xmlText = xmlAttribute.appendChild(ADMLDoc.createTextNode("CAT_" & lCategories(3,iCounter) & "_HELP"))
objTemp.Attributes.setNamedItem(xmlAttribute)
objTemp.text = "This Category configures the Values located under the [" & lCategories(2,iCounter) & "] Key." & vbcrlf & vbcrlf & _
"This policy file was generated by the REG_2_ADMXL tool" & vbcrlf & _
"This Freeware Tool can be downloaded from www.marianok.com.ar"


' <parentCategory ref="SAMPLE" />


next
end sub














sub WritePolicies()


dim iCounter
set ADMXParentNode = ADMXDoc.selectSingleNode("policyDefinitions/policies")
set ADMLStringNode = ADMLDoc.selectSingleNode("policyDefinitionResources/resources/stringTable")
set ADMLPresentationNode = ADMLDoc.selectSingleNode("policyDefinitionResources/resources/presentationTable")
for icounter = 1 to ubound(lPolicies,2)


' sWriteLog "Path: [" & lPolicies(1,iCounter) & "] " & vbcrlf & vbtab & _
' " GUID: [" & lPolicies(2,iCounter) & "] " & vbcrlf & vbtab & _
' " ParentGuid: [" & lPolicies(3,iCounter) & "]"


' Create the policy node On the ADMX File
Set objPolicy = ADMXDoc.createElement("policy")
ADMXParentNode.appendChild objPolicy




' Set the properties for the Policy node On the ADMX File
Set xmlAttribute = ADMXDoc.createAttribute("name")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("POL_" & lPolicies(2,iCounter)))
     objPolicy.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("displayName")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("$(string.POL_" & lPolicies(2,iCounter) & ")"))
     objPolicy.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("explainText")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("$(string.POL_" & lPolicies(2,iCounter) & "_HELP)"))
     objPolicy.Attributes.setNamedItem(xmlAttribute)


' Set xmlAttribute = ADMXDoc.createAttribute("valueName")
' Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode(lPolicies(1,iCounter)))
'      objPolicy.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("key")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode(lPolicies(7,iCounter)))
     objPolicy.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("class")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode(lPolicies(8,iCounter)))
     objPolicy.Attributes.setNamedItem(xmlAttribute)


'presentation="$(presentation.Sample_Textbox)"


'explainText="$(string.Sample_Textbox_Help)"








' Create the string node On the ADML File
Set objTemp = ADMLDoc.createElement("string")
ADMLStringNode.appendChild objTemp
Set xmlAttribute = ADMLDoc.createAttribute("id")
Set xmlText = xmlAttribute.appendChild(ADMLDoc.createTextNode("POL_" & lPolicies(2,iCounter)))
objTemp.Attributes.setNamedItem(xmlAttribute)
objTemp.text = lPolicies(1,iCounter)
Set objTemp = ADMLDoc.createElement("string")
ADMLStringNode.appendChild objTemp
Set xmlAttribute = ADMLDoc.createAttribute("id")
Set xmlText = xmlAttribute.appendChild(ADMLDoc.createTextNode("POL_" & lPolicies(2,iCounter) & "_HELP"))
objTemp.Attributes.setNamedItem(xmlAttribute)
objTemp.text = "This Policy configures the Value [" & lPolicies(4,iCounter) & "] located under the [" & lPolicies(7,iCounter) & "] Key." & vbcrlf & vbcrlf & _
"In the .REG file, this setting was defined as [" & lPolicies(5,iCounter) & "] and had the value [" & lPolicies(6,iCounter) & "] assigned." & vbcrlf & vbcrlf & _
"This policy file was generated by the REG_2_ADMXL tool" & vbcrlf & _
"This Freeware Tool can be downloaded from www.marianok.com.ar"


    
' Parent category so AD knows under what node of the tree to show this policy
Set objTemp = ADMXDoc.createElement("parentCategory")
objPolicy.appendChild objTemp
Set xmlAttribute = ADMXDoc.createAttribute("ref")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("CAT_" & lPolicies(3,iCounter)))
objTemp.Attributes.setNamedItem(xmlAttribute)


'This is another of those settings that I do not really know if i need, nor I know what are oll the posible values.
' I've found 2: "SUPPORTED_WindowsVista" and "SUPPORTED_ProductOnly". Because I do not really know what this last one means, I'll use the Vista one for everything.
' This goes on the ADMX file
Set objTemp = ADMXDoc.createElement("supportedOn")
objPolicy.appendChild objTemp
Set xmlAttribute = ADMXDoc.createAttribute("ref")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("windows:SUPPORTED_WindowsVista"))
objTemp.Attributes.setNamedItem(xmlAttribute)






Set objElements = ADMXDoc.createElement("elements")
objPolicy.appendChild objElements




' <presentation id="POL_A10AF138_0BBF_4285_85DC_A68ACC333E63">
' Create the presentation node
' This goes on the ADML file
Set objpresentation = ADMLDoc.createElement("presentation")
ADMLPresentationNode.appendChild objpresentation
Set xmlAttribute = ADMLDoc.createAttribute("id")
Set xmlText = xmlAttribute.appendChild(ADMLDoc.createTextNode("POL_" & lPolicies(2,iCounter)))
objpresentation.Attributes.setNamedItem(xmlAttribute)
' Set the presentation for the presentation node
Set xmlAttribute = ADMXDoc.createAttribute("presentation")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("$(presentation.POL_" & lPolicies(2,iCounter) & ")"))
     objPolicy.Attributes.setNamedItem(xmlAttribute)


' Here is the cheap and basic logic that determines what kind of interface the user will have in AD to enter the policy values.
' Currently, the logic is: If the data is a text gets a textbox, if the data is dword gets a numeric textbox. if the data is anything else, then becomes a text.
' I'm aware that this does not cover all posible cases, but it's a start and enought for my current needs.
' This goes on the ADMX file
if lcase(lPolicies(5,iCounter)) = "string" then
' <text id="Sample_TextboxPrompt" valueName="Example2textbox" />
Set objTemp = ADMXDoc.createElement("text")
objElements.appendChild objTemp
Set xmlAttribute = ADMXDoc.createAttribute("id")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("TXT_" & lPolicies(2,iCounter)))
objTemp.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("valueName")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode(lPolicies(1,iCounter)))
     objTemp.Attributes.setNamedItem(xmlAttribute)


            ' The strings for the previous data must be saved as well
            ' This goes on the ADML file
         ' <TextBox refId="TXT_EA865626_37FD_48A5_8CFE_77702C6D648D">
         ' Create the string node
         Set objTextbox = ADMLDoc.createElement("textBox")
         objpresentation.appendChild objTextbox
    Set xmlAttribute = ADMLDoc.createAttribute("refId")
     Set xmlText = xmlAttribute.appendChild(ADMLDoc.createTextNode("TXT_" & lPolicies(2,iCounter)))
     objTextbox.Attributes.setNamedItem(xmlAttribute)


     ' <label>Country</label>
     Set objTemp = ADMLDoc.createElement("label")
    objTextbox.appendChild objTemp
     objTemp.text=lPolicies(1,iCounter)
  

     ' <defaultValue>US</defaultValue>
    Set objTemp = ADMLDoc.createElement("defaultValue")
     objTextbox.appendChild objTemp
     objTemp.text=lPolicies(6,iCounter)


elseif lcase(lPolicies(5,iCounter)) = "dword" then
' <decimal id="DXT_CECFD96A_F36B_4AB8_8B0F_57F8BAB84D08" key="SOFTWARE\INTERLINK\MSCLink\90\Config" valueName="User_Id" />
Set objTemp = ADMXDoc.createElement("decimal")
objElements.appendChild objTemp
Set xmlAttribute = ADMXDoc.createAttribute("id")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("DXT_" & lPolicies(2,iCounter)))
objTemp.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("valueName")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode(lPolicies(1,iCounter)))
     objTemp.Attributes.setNamedItem(xmlAttribute)




' <decimalTextBox refId="DXT_CECFD96A_F36B_4AB8_8B0F_57F8BAB84D08">
' </decimalTextBox>
' Create the decimalTextBox node ' This goes on the ADML file
     Set objTextbox = ADMLDoc.createElement("decimalTextBox")
     objpresentation.appendChild objTextbox
    Set xmlAttribute = ADMLDoc.createAttribute("refId")
     Set xmlText = xmlAttribute.appendChild(ADMLDoc.createTextNode("DXT_" & lPolicies(2,iCounter)))
     objTextbox.Attributes.setNamedItem(xmlAttribute)


elseif left(lcase(lPolicies(5,iCounter)),3) = "hex" then
' This is a Hexadecimal Value ... I have not found a way to handle them so, for now, I will just handle them as string
Set objTemp = ADMXDoc.createElement("text")
objElements.appendChild objTemp
Set xmlAttribute = ADMXDoc.createAttribute("id")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode("HXT_" & lPolicies(2,iCounter)))
objTemp.Attributes.setNamedItem(xmlAttribute)
Set xmlAttribute = ADMXDoc.createAttribute("valueName")
Set xmlText = xmlAttribute.appendChild(ADMXDoc.createTextNode(lPolicies(1,iCounter)))
     objTemp.Attributes.setNamedItem(xmlAttribute)




' <decimalTextBox refId="DXT_CECFD96A_F36B_4AB8_8B0F_57F8BAB84D08">
' </decimalTextBox>
' Create the decimalTextBox node ' This goes on the ADML file
     Set objTextbox = ADMLDoc.createElement("TextBox")
     objpresentation.appendChild objTextbox
    Set xmlAttribute = ADMLDoc.createAttribute("refId")
     Set xmlText = xmlAttribute.appendChild(ADMLDoc.createTextNode("HXT_" & lPolicies(2,iCounter)))
     objTextbox.Attributes.setNamedItem(xmlAttribute)
else
wscript.echo "UNKNOWN DATATYPE: " & lPolicies(5,iCounter)
' Create the decimalTextBox node
Set objTemp = ADMXDoc.createElement("Text")
objElements.appendChild objTemp
Set objTextbox = ADMLDoc.createElement("Text")
objpresentation.appendChild objTextbox


end if


    ' If we had a Boolean data or something that get's turn ON/OFF we would use this, but I have not find a good way to determine that on the fly, so I just leave it here in case i ever do.
    ' Set objTemp = ADMXDoc.createElement("enabledValue")
    ' objPolicy.appendChild objTemp
    ' Set objTemp = ADMXDoc.createElement("disabledValue")
    ' objPolicy.appendChild objTemp               
    

next
end sub




function GetClass(byref sKey)


dim iBar
    ' This function determines the scope ("CLASS" in the ADMX file) of the KEY.
    ' As allwyass, my logic is really basic: everything is BOTH, except for HKCU that get's assigned USER and HKLM that get's assigned Machine.
dim sTempClass
sTempClass = "Both"
if left(sKey,17)="HKEY_CURRENT_USER" then
sKey=right(sKey,len(sKey)-17-1)
sTempClass="User"
elseif left(sKey,4)="HKCU" then
sKey=right(sKey,len(sKey)-4-1)
sTempClass="User"
elseif left(sKey,18)="HKEY_LOCAL_MACHINE" then
sKey=right(sKey,len(sKey)-18-1)
sTempClass="Machine"
elseif left(sKey,4)="HKLM" then
sKey=right(sKey,len(sKey)-4-1)
sTempClass="Machine"
elseif left(sKey,17)="HKEY_CLASSES_ROOT" then
sKey= "SOFTWARE\Classes" & right(sKey,len(sKey)-17)
sTempClass="Both"
elseif left(sKey,4)="HKCR" then
sKey="SOFTWARE\Classes" & right(sKey,len(sKey)-4)
sTempClass="Both"
elseif left(sKey,19)="HKEY_CURRENT_CONFIG" then
sKey= "SYSTEM\CurrentControlSet\Hardware Profiles\Current" & right(sKey,len(sKey)-19)
sTempClass="Machine"
elseif left(sKey,4)="HKCC" then
sKey= "SYSTEM\CurrentControlSet\Hardware Profiles\Current" & right(sKey,len(sKey)-4)
sTempClass="Machine"
elseif left(sKey,10)="HKEY_USERS" then
sKey=right(sKey,len(sKey)-10-1)
iBar = instr(1,sKey,"\")
sKey=right(sKey,len(sKey)-iBar)
sTempClass="Both"
elseif left(sKey,3)="HKU" then
sKey=right(sKey,len(sKey)-3-1)
iBar = instr(1,sKey,"\")
sKey=right(sKey,len(sKey)-iBar)
sTempClass="Both"
end if
GetClass = sTempClass
end function








function GetName(sPath)
    ' This function retrieves the name of the current KEY based on the full PAth
iLast = InStrRev(sPath,"\")
sTemp="\\\\"
if iLast > 0 then
sTemp=right(sPath,len(sPath)-iLast)
end if
GetName=sTemp
end function




function GetParentGUID(sPath,sName)
    ' This function finds the Parent of the current registry KEY based on the PATH structure.
sFind = replace(sPath,"\" & sName,"")
sTemp="MARIANOKS_XML_2_ADMXL"
for icounter = 1 to ubound(lCategories,2)
if lcase(sFind) = lcase(lCategories(2,iCounter)) then
sTemp= lCategories(3,iCounter)
end if
next
GetParentGUID=sTemp
end function








function GenerateGUID()
    ' All settings in the GP must be unique, not only for this particular ADMX but for any other on the system.
    ' The best way to ensure that is to use GUIDs to name the objects.
    Set TypeLib = CreateObject("Scriptlet.TypeLib")
sTemp = TypeLib.GUID
sTemp = replace(sTemp,"{","")
sTemp = replace(sTemp,"}","")
sTemp = replace(sTemp,"-","_")
GenerateGUID = left(sTemp, len(sTemp)-2)


end function

1 comment: