The word has been out on fellow CTP Carl Webster’s latest secret project Group Policy Settings Reference for Citrix XenApp and XenDesktop for a couple of weeks now and I really love how he keeps giving all participants the credits they deserve. So let me start by giving a lot of kudos as well to Markus Zehnle, Carl Behrent and Jon Falgout for their help in tracking all those registry settings!
When Carl shared the fact that he was starting this new secret project I felt really honored to be one of the “chosen ones” to help his track down all the registry settings that are created on the target machines. And at first it seemed that our job would be an easy one as Citrix has a CTX135039 – Citrix Policy Reference article posted in their Support Knowledge Center. Unfortunately that one is not updated in a very long time (it doesn’t go beyond XenApp 6.5 and XenDesktop 5.6), so I was forced to manually track down the effect of each configured policy setting in the registry of my test machines.
What about those ADMX files?
And then I thought I had the brilliant idea that ADMX files are nothing more than XML based files these days, so why not build myself a small translating PowerShell Script to turn an ADMX file into a more readable CSV (Reference) file and make those registry settings more visible. And even though it is a great thought that was easily scripted, there is just one minor catch … as Carl pointed out to me … the Citrix Policy Settings in the GPMC or Studio are not based on ADMX files! The only ADMX files that Citrix provides as separate downloads focus on specific client settings, like the Citrix Receiver, Federated Authentication Services, ShareFile, Workspace Environment Manager and Profile Manager settings. Even though it would not make the job easier for the XenDesktop and XenApp policy settings, my own curiosity was triggered to see how easily an ADMX file could be ‘read’ to get the required information. Even though I got the basics figured out after my first runs, it turned out to be a bit more work to translate each element type into the correct format and process the different settings for text, list and select boxes.
What the script does
To explain what the script does, I’ll use a policy setting from the FederatedAuthenticationServices.admx file to dive into the processing of the script.
The Federated Authentication Services In-session Certificates policy setting
As I’m working on a Citrix Federated Authentication Services design these days I just happened to start my script with the ADMX and ADML files for these policy settings. So let’s zoom in on the In-session Certificates policy setting that I can configure.
As you can see, the policy can be used to configure three settings on how the smartcard logon certificate that FAS creates can be used within the user session. We can specify the Prompt Scope by choosing an option from a drop down list, enter a value in a textbox for the Consent timeout, and tick a selectbox to have the session disconnect when it is locked.
The resulting registry settings
Each policy setting results in one or more registry settings to influence the behavior of the operating system or application. In our example, the policy settings will let the VDA know how to handle the smartcard logon certificate within a user session and whether or not to disconnect when the session is locked.
And even though I had a lot of fun tracking down the location of each registry setting by simple enabling the policy setting and checking the registry on the VDA, I knew there had to be an easier way with the ADML file at hand.
The ADMX file
So let’s zoom in on the part of the ADML file that focuses on that policy setting:
<?xml version="1.0" encoding="utf-8"?> <policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.0" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions"> <categories> <category name="CREDENTIAL_MAPPING_SERVICE" displayName="$(string.CITRIX_AUTHENTICATION)" explainText="$(string.CITRIX_AUTHENTICATION_HELP)" > <parentCategory ref="citrix:CITRIX_COMPONENTS" /> </category> </categories> <policies> <policy name="VirtualSmartcard" class="Machine" displayName="$(string.VirtualSmartcard)" explainText="$(string.VirtualSmartcard_Help)" presentation="$(presentation.VirtualSmartcard)" key="Software\Policies\Citrix\Authentication\VirtualSmartcard" valueName="Enabled"> <parentCategory ref="CREDENTIAL_MAPPING_SERVICE" /> <supportedOn ref="windows:SUPPORTED_ProductOnly" /> <enabledValue> <decimal value="1" /> </enabledValue> <disabledValue> <decimal value="0" /> </disabledValue> <elements> <enum id="ConsentScope" valueName="ConsentScope"> <item displayName="$(string.ConsentScope_None)"> <value> <string>NoConsentRequired</string> </value> </item> <item displayName="$(string.ConsentScope_Process)"> <value> <string>PerProcess</string> </value> </item> <item displayName="$(string.ConsentScope_Session)"> <value> <string>PerSession</string> </value> </item> </enum> <text id="ConsentTimeout" valueName="ConsentTimeout" required="true" maxLength="8"/> <boolean id="DisconnectOnLock" valueName="DisconnectOnLock"> <trueValue> <decimal value="1" /> </trueValue> <falseValue> <decimal value="0" /> </falseValue> </boolean> </elements> </policy> </policies> </policyDefinitions>
The first line that is highlighted gives us a lot of information about the policy setting as it provides us with the policy name, whether it’s a computer or user setting (or both), and the registry key and value names. So it looks like an easy script to build, right?
The part that took the most time in this script is the translation of all the different elements that can be used with the policy settings. Our example already uses a dropdownlist, a text box and a select box to create three registry keys.
The elements
In order to understand how each element of the policy setting is translated to a registry setting, we need to dive deeper into the ADMX file and check out the structure of the different elements that are listed underneath the policy. If we look at highlighted line 19 of the ADMX file, our first element is an enum element. Erhm, that didn’t make it obvious for me right away. But I got lucky as I discovered that Microsoft has a ADMX syntax page that explains all the different elements and how they are translated by the GPO Editor to registry settings. I don’t think the script has all the elements included in it just yet, but the main settings I encountered are now included and will translate the elements to the right registry values and possible value data.
The ADML File
The ADML file is a language file and used to retrieve the actual language specific decriptions for all the labels in the ADMX file, like the displayName for our highlighted policy in the ADMX file.
<?xml version="1.0" encoding="utf-8"?> <policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.0" schemaVersion="1.0" xmlns="http://www.microsoft.com/GroupPolicy/PolicyDefinitions"> <displayName>Citrix Federated Authentication Service configuration</displayName> <description>This group policy template configures the Citrix Federated Authentication Service</description> <resources> <stringTable> <string id="VirtualSmartcard">In-session Certificates</string> <string id="supported_productonly">Only works on Windows 2012R2.</string> <string id="ConsentScope_None">No consent required</string> <string id="ConsentScope_Process">Per-process</string> <string id="ConsentScope_Session">Per-session</string> </stringTable> <presentationTable> <presentation id="VirtualSmartcard"> <dropdownList refId="ConsentScope" defaultItem="1">Prompt Scope</dropdownList> <textBox refId="ConsentTimeout" ><label>Consent timeout (seconds):</label></textBox> <checkBox refId="DisconnectOnLock">Disconnect on lock</checkBox> </presentation> </presentationTable> </resources> </policyDefinitionResources>
The en-US translation for that is highlighted in line 7 of the ADML file and shows us the name of the policy as “In-session Certificates”. Seems correct if we check the policy setting image :-).
And the labels for the different elements can be found under the presentation XML element once you figure out that enum is linked to dropdownList, text is linked to textBox, and boolean is linked to checkBox. Guess keeping them the same in both files would not have made this as much fun as this puzzle became ;-).
The CSV output
And to give you an idea of the output the script generates, here’s a screenshot of some of the output:
My PowerShell script
I had a lot of fun building this script and know I probably missed a couple of policy elements or could even retrieve more information from the ADMX files as it does right now. But this version made my life easy enough to quickly provide Carl Webster the intel he needed for his Group Policy Settings Reference for Citrix XenApp and XenDesktop project. So for now I just leave you with the challenge of sharing your improvements for the script with me on GitHub or in the comments.
Feel free to use this PowerShell script (at your own risk 😉 ) to create a reference file to log those policy settings used in Production environments or to add the desired policy settings as an appendix to your technical design documents for easy implementations in future projects.
To download the latest version of the script, check out the GitHub repository at:
https://github.com/cognitionIT/ADMXReader
I know I’m not a PowerShell guru and the script can probably use some tweaking and functions, but here it is, all 519 lines of code used in the script to translate ADMX files into a more readable CSV file:
<# .SYNOPSIS Citrix ADMX Policy information converter. .DESCRIPTION The Citrix ADMX Policy information converter converts the ADMX XML format to a more readable CSV file with policy and registry information. .PARAMETER <Parameter_Name> <Brief description of parameter input required. Repeat this attribute if required> .PARAMETER ADMXFolder The Folder containing the ADMX files that will be processed by this script. Please include a language subfolder for the corresponding ADML files. .PARAMETER OutputCSVFile The name (and extension) of the output CSV file created by the script. .PARAMETER language The language (and subfolder name) of the corresponding ADML files to be used by the script. Only en-US has been used during script testing and is included in the selectable languages. .OUTPUTS Policy information CSV file stored in the root of the provided policy directory. .NOTES Version: 1.1 Author: Esther Barthel, MSc Creation Date: 2017-02-10 Purpose/Change: Initial script development Update Date: 2017-03-19 Purpose/Change: Adjustments after first Windows ADMX files run Copyright (c) cognition IT. All rights reserved. Based upon this reference material: https://technet.microsoft.com/en-us/library/cc731761(v=ws.10).aspx https://technet.microsoft.com/en-us/library/cc771659(v=ws.10).aspx (ADMX syntax) .EXAMPLE ADMXReader_v1_1.ps1 -ADMXFolder $($env:windir)\policyDefinitions -OutputCSVFile "ADMXOutput.csv" -language en-US #> [CmdletBinding()] # Declaring script parameters Param( # The location of folder that contains the ADMX files to be processed [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()][string]$ADMXFolder="C:\Temp\ADMXReader\ADMXFiles", # The name of the CSV Output file that is generated by the script [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()][string]$OutputCSVFile="C:\Temp\ADMXReader\ADMXFiles\Script_Output_Test.csv", # Language of the ADML file to be used (located in the language subfolder) [Parameter(Mandatory=$true)] [ValidateSet("en-US")] [string]$language="en-US" ) #requires -version 3 Clear-Host #region---------------------------------------------------------[Initialisations]-------------------------------------------------------- $rowCount = 0 $policyCount = 0 $elementCount = 0 $dropdownListValues = "" $itemCount = 0 #endregion #region----------------------------------------------------------[Declarations]---------------------------------------------------------- # Creating the DataTable object $table= New-Object System.Data.DataTable # Setting the table headers [void]$table.Columns.Add("ADMX") [void]$table.Columns.Add("Parent Category") [void]$table.Columns.Add("Name") [void]$table.Columns.Add("Display Name") [void]$table.Columns.Add("Class") [void]$table.Columns.Add("Explaining Text") [void]$table.Columns.Add("Supported On") [void]$table.Columns.Add("Type") [void]$table.Columns.Add("Label") [void]$table.Columns.Add("Registry Key") [void]$table.Columns.Add("Value Name") [void]$table.Columns.Add("Possible Values") If ($PSBoundParameters['Debug']) { # Changing Debugging from default 'Inquire' setting (with prompted actions per Write-Debug line) to continu to generate Debug messages without the prompts. $DebugPreference = [System.Management.Automation.ActionPreference]::Continue } #endregion #region-----------------------------------------------------------[Execution]------------------------------------------------------------ If (!(Test-Path -Path $ADMXFolder)) { Throw "Policy Directory $ADMXFolder NOT Found. Script Execution STOPPED." } # Retrieving al the ADMX files to be processed $admxFiles = Get-ChildItem $ADMXFolder -filter *.admx Write-Output ($admxFiles.Count.ToString() + " ADMX files found in """ + $ADMXFolder + """") # Checking for the Windows and Citrix supportedOn vendor definition files If (Test-Path("$ADMXFolder\$language\Windows.adml")) { $supportedOnWindowsTableFile = Get-Content "$ADMXFolder\$language\Windows.adml" } If (Test-Path("$ADMXFolder\$language\CitrixBase.adml")) { $supportedOnCitrixTableFile = Get-Content "$ADMXFolder\$language\CitrixBase.adml" } ForEach ($file in $admxFiles) { #Proces each file in the directory Write-Output ("*** Processing file " + $file.Name) $data=Get-Content "$ADMXFolder\$($file.Name)" $lang=Get-Content "$ADMXFolder\$language\$($file.Name.Replace(".admx",".adml"))" # Retrieve all information groups from the specific ADMX file (for easy searches) $stringTableChilds = $lang.policyDefinitionResources.resources.stringTable.ChildNodes $presentationTableChilds = $lang.policyDefinitionResources.resources.presentationTable.ChildNodes $supportedOnDefChilds = $data.policyDefinitions.supportedOn.definitions.ChildNodes $categoryChilds = $data.policyDefinitions.categories.ChildNodes #Initializing the policy Counter $policyCount = 0 # Processing each policy (ChildNode) of the policies Node $data.PolicyDefinitions.policies.ChildNodes | ForEach-Object { # Get current policy node $policy = $_ If ($policy -ne $null) { # Processing policy nodes with a name, other than #comment (making sure the comment tags in the file are ignored!) If ($policy.Name -ne "#comment") { $policyCount = $policyCount + 1 Write-Output "* Processing policy $($policy.Name)" # Retrieving policy information from node $polDisplayName = ($stringTableChilds | Where-Object { $_.id -eq $policy.displayName.Substring(9).TrimEnd(')') }).InnerText # Getting the displayName from the StringTable Write-Debug "displayName for $($policy.Name) is ""$polDisplayName""" $explainText = ($stringTableChilds | Where-Object { $_.id -eq $policy.explainText.Substring(9).TrimEnd(')') }).InnerText # Getting the explainText from the StringTable Write-Debug "explainText for $($policy.Name) is ""$explainText""" $regkey = $policy.key Write-Debug "registry key for $($policy.Name) is ""$regkey""" #region retrieving supportedOn information If ($policy.SupportedOn.ref.Contains(":")) { $supportedOnVendor=$policy.SupportedOn.ref.Split(":")[0] Switch ($supportedOnVendor.ToLower()) { "windows" { # Using the Windows supportedOn information from the Windows.ADMX file If ($supportedOnWindowsTableFile -ne $nulll) { $supportedOnTableChilds = $supportedOnWindowsTableFile.policyDefinitionResources.resources.stringTable.ChildNodes } } "citrix" { # Using the Citrix supportedOn information from the Citrix.ADMX file If ($supportedOnCitrixTableFile -ne $null) { $supportedOnTableChilds = $supportedOnCitrixTableFile.policyDefinitionResources.resources.stringTable.ChildNodes } } default { # Use the specific supportedOn information from the current ADMX file $supportedOnTableChilds = $stringTableChilds } } $supportedOnID=$policy.SupportedOn.ref.Split(":")[1] $supportedOn=($supportedOnTableChilds | Where-Object { $_.id -eq $supportedOnID }).InnerText If ([string]::IsNullOrEmpty($supportedOn)) { $supportedOn=($stringTableChilds | Where-Object { $_.id -eq $supportedOnID }).InnerText } } Else # no ':' in supportedOn information, find name in right supportedOn identity { $supportedOnID = ($supportedOnDefChilds | Where-Object { $_.Name -eq $policy.supportedOn.ref }).displayName If ($supportedOnID -ne $null) { # supportedOn displayname found, find description text in stringTable ChildNodes $supportedOn = ($stringTableChilds | Where-Object { $_.id -eq $supportedOnID.Substring(9).TrimEnd(')') }).InnerText } Else { # supported on information not found $supportedOn = "*unknown*" } } Write-Debug "SupportedOn for $($policy.Name) is ""$supportedOn""" #endregion retrieving supportedOn information #region retrieving parentCategory information If ($policy.parentCategory.ref.Contains(":")) { $parentCategoryID=$policy.parentCategory.ref.Split(":")[1] $parentCategory=($stringTableChilds | Where-Object { $_.id -eq $parentCategoryID }).InnerText } Else # no ':' in categoryParent information, find name in right Category identity { $parentCategoryID = ($categoryChilds | Where-Object { $_.Name -eq $policy.parentCategory.ref }).displayName If ($parentCategoryID -ne $null) { # parentCategory displayname found, find description text in stringTable ChildNodes $parentCategory = ($stringTableChilds | Where-Object { $_.id -eq $parentCategoryID.Substring(9).TrimEnd(')') }).InnerText } Else { # Check the ADML file for Category Label $parentCategory = ($stringTableChilds | Where-Object { $_.id -eq $policy.parentCategory.ref }).'#text' } } Write-Debug "parentCategory for $($policy.Name) is ""$parentCategory""" #endregion retrieving parentCategory information #region retrieving policy element information $elementCount = 0 $policy.elements.ChildNodes | ForEach-Object { $element = $_ If ($element -ne $null) { $elementCount = $elementCount + 1 $elementLabelText = "" Switch ($element.Name) { "#comment" { # Do Nothing/Ignore $elementType = "comment" $valueName = $element.valueName $dropdownListValues = "" } "list" { # Process list element # Retrieve label, based on element.id and policy.name $oList = (($presentationTableChilds | Where-Object { $_.id -eq $policy.presentation.Substring(15).TrimEnd(')')}).ChildNodes | Where-Object {$_.refId -eq $element.id}) # the list element has it's own registry key and different value attribute $elementType = $oList.Name $elementLabelText = $oList.InnerText If (!([string]::IsNullOrEmpty($element.valuePrefix))) { $valueName = $element.valuePrefix + " (prefix)" $regkey = $element.key } Else { $valueName = "(list)" $regkey = $element.key } $dropdownListValues = "" } "text" { # process text element # Retrieve label, based on element.id and policy.name $oText = (($presentationTableChilds | Where-Object { $_.id -eq $policy.presentation.Substring(15).TrimEnd(')')}).ChildNodes | Where-Object {$_.refId -eq $element.id}) # the text element has it's own registry key and different value attribute $elementType = $oText.Name $elementLabelText = $oText.label $valueName = $element.valueName $dropdownListValues = "" If (($elementType -eq "comboBox") -and (!([string]::IsNullOrEmpty($oText.suggestion)))) { $dropdownListValues = ("Suggestion: " + $oText.suggestion) $valueName = $valueName + " (comboBox)" } } "enum" { # process enum element $elementType = "dropdownList" # Retrieve label, based on element.id and policy.name $oEnum = (($presentationTableChilds | Where-Object { $_.id -eq $policy.presentation.Substring(15).TrimEnd(')')}).ChildNodes | Where-Object { $_.refId -eq $element.id}) $elementType = $oEnum.Name $elementLabelText = $oEnum.InnerText # Retrieving the possible items from the dropdownlist $dropdownListValues = "Listitems:" $itemCount = 0 $element.ChildNodes | ForEach-Object { $item = $_ If (($item -ne $null) -and ($item.name -ne "#comment")) { $itemCount = $itemCount + 1 $itemLabelText = ($stringTableChilds | Where-Object { $_.id -eq $item.displayName.SubString(9).TrimEnd(')') }).InnerText $dropdownListValues = ($dropdownListValues + " `n """ + $item.value.string + """ = """ + $itemLabelText + """") } } # adding the dropdownlist items to the valueName $valueName = ($element.valueName + " (dropdownList)") Write-Debug "$itemCount items processed" } "boolean" { # process boolean element # Retrieve label, based on element.id and policy.name $oBoolean = (($presentationTableChilds | Where-Object { $_.id -eq $policy.presentation.Substring(15).TrimEnd(')')}).ChildNodes | Where-Object {$_.refId -eq $element.id}) $elementType = $oBoolean.Name $elementLabelText = $oBoolean.InnerText $valueName = $element.valueName $dropdownListValues = "" } "decimal" { $elementType = "decimalTextBox" # Retrieve label, based on element.id and policy.name $oDecimal = (($presentationTableChilds | Where-Object { $_.id -eq $policy.presentation.Substring(15).TrimEnd(')')}).ChildNodes | Where-Object {$_.refId -eq $element.id}) $elementType = $oDecimal.Name $elementLabelText = $oDecimal.InnerText $valueName = $element.valueName $dropdownListValues = "" } "multiText" { $elementType = "multiText" # Retrieve label, based on element.id and policy.name $oMultiText = (($presentationTableChilds | Where-Object { $_.id -eq $policy.presentation.Substring(15).TrimEnd(')')}).ChildNodes | Where-Object {$_.refId -eq $element.id}) $elementType = $oMultiText.Name $elementLabelText = $oMultiText.InnerText $valueName = $element.valueName $dropdownListValues = "" } default { # unknown element $elementType = "unknown" $elementLabelText = "unknown label" $valueName = $element.valueName $dropdownListValues = "" } } } Else { # Including the basic policy (level) setting to the table $elementType="policy setting" $valueName = $policy.ValueName $dropdownListValues = "" } Write-Debug "elementType is ""$elementType"", value is $valueName" If (($policy.presentation -eq "") -or ($policy.presentation -eq $null)) { $policyText = "" } Else { $policyText = " (policy """ + $policy.presentation.Substring(15).TrimEnd(')') + """, element """ + $element.id + """) " } Write-Debug ("Label $elementCount : " + $elementLabelText + $policyText) If ($elementType -ne "comment") { # Updated: New rows enumeration [void]$table.Rows.Add( $file.Name, $parentCategory, $policy.Name, $polDisplayName, $policy.class, $explainText, $supportedOn, $elementType, $elementLabelText, $regkey, $valueName, $dropdownListValues) $rowCount = $rowCount + 1 } } #endregion retrieving policy element information # policy & elements are processed Write-Verbose ($elementCount.ToString() + " element(s) processed") #region retrieving policy enabledList information $enabledListCount = 0 $policy.enabledList.ChildNodes | ForEach-Object { $enabledItem = $_ If ($enabledItem -ne $null) { $enabledListCount = $enabledListCount + 1 $enabledItemType = $enabledItem.value.ChildNodes[0].Name $elementType = ("enabledList " + $enabledItemType) Switch ($enabledItemType) { "string" { $dropdownListValues = ("value: " + $enabledItem.value.string) } "decimal" { $dropdownListValues = ("value: " + $enabledItem.value.decimal.value.ToString()) } default { $dropdownListValues = "" } } $regkey = $enabledItem.key $valueName = $enabledItem.valueName # Updated: New rows enumeration [void]$table.Rows.Add( $file.Name, $parentCategory, $policy.Name, $polDisplayName, $policy.class, $explainText, $supportedOn, $elementType, "", $regkey, $valueName, $dropdownListValues) $rowCount = $rowCount + 1 } Else { # No enabledList items to process $elementType="no enabledList items for policy" $valueName = $policy.ValueName } # Write-Debug "enabledListItem is ""$elementType"", value is $valueName" } #endregion retrieving policy enabledList information # enabledList items are processed #region retrieving policy disabledList information $disabledListCount = 0 $policy.disabledList.ChildNodes | ForEach-Object { $disabledItem = $_ If ($disabledItem -ne $null) { $disabledListCount = $disabledListCount + 1 $disabledItemType = $disabledItem.value.ChildNodes[0].Name $elementType = ("disabledList " + $disabledItemType) Switch ($disabledItemType) { "string" { $dropdownListValues = ("value: " + $disabledItem.value.string) } "decimal" { $dropdownListValues = ("value: " + $disabledItem.value.decimal.value) } default { $dropdownListValues = "" } } $regkey = $disabledItem.key $valueName = $disabledItem.valueName # Updated: New rows enumeration [void]$table.Rows.Add( $file.Name, $parentCategory, $policy.Name, $polDisplayName, $policy.class, $explainText, $supportedOn, $elementType, "", $regkey, $valueName, $dropdownListValues) $rowCount = $rowCount + 1 } Else { # No disabledList items to process $elementType="no disabledList items for policy" $valueName = $policy.ValueName } Write-Debug "disabledListItem is ""$elementType"", value is $valueName" } #endregion retrieving policy enabledList information # enabledList items are processed # counter to track the total amount of processed policies in the ADMX files $policyCount = $policyCount + 1 } Else { Write-Debug ("Comment policies ChildNode found, node NOT processed") } } } Write-Output ("A total of " + $policyCount.ToString() + " policy settings were processed in file " + $file.Name) } Write-Output ("=> A total of " + $rowCount + " policy settings were translated into the CSV file.") # Updated: Exporting the data with the right Ecoding (see XML file header: <?xml version="1.0" encoding="utf-8"?> and using the Locale Delimiter for the CSV file) $table | Export-Csv $OutputCSVFile -NoTypeInformation -Encoding UTF8 -UseCulture -Force Write-Output ("===> Results were saved in """ + $OutputCSVFile + """")
To download the latest version of the script, check out the GitHub repository at:
https://github.com/cognitionIT/ADMXReader
Awesome script and work Esther! certainly not a n00b script :o) – Nice Job!
Thanks for the boost of PowerShell confidence Dave!
Very nice script Esther! I have found though that the output is not 100% when run against the Citrix Connection Quality Indicator admx files (CitrixCQI.admx). I haven’t had a chance to debug as yet, but the timing of this script was perfect as I need to deploy the Connection Quality Indicator using GPP’s instead of the admx files.
Cheers,
Jeremy
Hi Jeremy, you have made me very curious about the bug you found, so I’ve sent you an email and would love to help out and improve my script 🙂
Pingback: PowerShell: Updated ADMX Translation Script | virtuEs.IT