Wow, two blogs in one day! I wanted to get this blog out before I leave for Synergy. I would like to note that I will be working in the Tech Lab at Synergy; feel free to stop by and pick my brain about the XenApp SDK!
Last time I covered the concepts around configuring settings via policy. This time, let’s “put the rubber to the road” and create scripts that use the Group Policy Provider to set settings and solve real-world problems.
First, to cover a common question: why are server, farm, and user settings configured via a provider, while all other settings are configured via cmdlets? The answer is that the Microsoft design for implementing PowerShell snap-ins makes it much easier to create dynamic providers than it is to create dynamic cmdlets. The Citrix Group Policy provider is able to dynamically grow to encompass different combinations of multiple Citrix products installed throughout an organization. This is important for a component that is able to configure settings on many products. Had this been implemented as a set of cmdlets, we at Citrix would have had to create a large number of variations of the cmdlets in order to support all combinations of multiple products; alternatively, we would have had to create cmdlets that encompass all known product settings but which have many settings that may not be applicable based on which products are actually installed. Such a component would become very difficult to manage across development teams and product cycles.
So then, why not configure everything using providers? The short answer is that we found that cmdlets provide better overall discoverability and usability. It is not always clear when using a provider how you can accomplish a task, or even what operations are supported at a given level or by a given object. Until PowerShell 2.0 was released (which was quite recently, and well into XenApp 6 development), providers were not able to have rich, context-sensitive help; whereas with cmdlets, it is simple to find what is available, and it is easy and intuitive to get full and detailed help for those cmdlets. Some operations like enumerating, then filtering, then operating on the resulting set of objects are much simpler to compose using cmdlets as well.
The end result is that we use both models within our SDK, with the “best tool for the job” based on whether you are configuring settings within a policy, or settings on an object.
When using the provider, the basic steps for configuring a setting are:
- Find or create a PSDrive for editing the GPO where you want the settings stored.
- Within the PSDrive, find or create a policy with the appropriate filters.
- Within the policy, find the setting.
- Set the setting’s State and Value properties.
Let’s implement the examples from the last blog post. Quoting myself:
Example: I wish to set the license server for all servers in my company to “ctxlicense.company.com”.
Since the scope of the setting is potentially larger than a single farm, I should set this setting within a Domain GPO that is linked to a scope encompassing my entire organization. I do not wish any filtering, since I want all servers to be included in the scope, so I need to set the setting within the GPO’s “Unfiltered” policy. I need to set the setting’s State to “Enabled” so that it will apply the value. And I need to set the Value accordingly.
Let’s assume my GPO is named “Entire Organization”. I should note that this is a normal GPO; there is nothing special required of a GPO that you wish to put Citrix settings into. AD administrators should be happy to note that Citrix has not made any changes whatsoever to AD schema.
The first step for my script is to create a PSDrive, since I haven’t created one for my domain GPO yet. The command to do this within PowerShell is New-PSDrive. But the options to this command vary based on the provider, so let’s first examine the help for this cmdlet when using it for the Citrix Group Policy Provider:
Get-Help New-PSDrive -Path LocalFarmGpo: -Detailed
Deducing from this help (specifically, Example 2 within the help), I know that I can create my PSDrive with this command:
New-PSDrive -Name DomainGpo -PSProvider CitrixGroupPolicy --Root \ -DomainGpo "Entire Organization"
Once this command executes, I can navigate the provider as if it were a drive on the system:
Here, you’ll see “Computer” and “User”. Within these are settings that apply to the entire computer, and settings that apply to user sessions, respectively. The license server applies to the entire computer so it is a Computer setting. Continuing:
Here, you’ll find the policies in the GPO. There is a single policy by default, “Unfiltered”. This will be sufficient for our purposes.
Again, you’ll see a single item named “Settings”. If you were in any policy other than “Unfiltered” you would also see an item named “Filters”.
Now you will see that you have begun navigating through setting categories. These categories match those in the Policy UI within the Delivery Services Console and GPEdit.
Finally you have reached the actual setting that we care about: LicenseServerHostName. You will see that this setting has a State which is “NotConfigured” and a Value which is empty.
Let’s find out more information about this setting:
Get-Help Set-ItemProperty -Path LicenseServerHostName -Detailed
In the “DESCRIPTION” section, you will find text titled “Setting Description” that is relevant to the specific setting. You will also find setting-specific examples. This is an example of PowerShell 2.0’s support for context-sensitive Provider help. We have built help into the Group Policy Provider for all Citrix settings.
We need to set the value appropriately:
Set-ItemProperty LicenseServerHostName -Name Value -Value "ctxlicense.company.com"
This may be a little confusing at first, especially having to type the word “Value” twice. The first “Value” means that you are setting the property named Value and the second “Value” means you are setting the value of the property.
If everything goes well, you should see no output. This follows a general PowerShell guideline: typically, when a command gets or creates an object, it outputs the object that was retrieved or created; but when a command updates or deletes an object, it does not (by default) output the object that was updated or deleted. You can override this behavior with the “-Passthru” command-line parameter.
Let’s examine our work:
In the output, you should see that the LicenseServerHostName setting has now changed its Value to “ctxlicense.company.com”, and the State has also automatically switched to “Enabled”. As a shortcut, the SDK will always automatically enable a setting when you set its Value.
Assuming this GPO is actually linked to the appropriate item(s) in AD, your configuration is done. After an AD replication delay, all Citrix servers in your organization will automatically start using the license server you specified!
Moving on to the next example presented in the previous blog:
Example: I wish to disable client drive mapping for applications in my farm whenever clients connect from a subnet.
Since the scope of the setting is a single farm, I should set this setting with the Farm GPO for that farm. In addition, I need to filter the setting based on Client IP, so I cannot set the setting within the “Unfiltered” policy; I need to create another policy in the GPO. I need to set the setting’s State to “Prevent” so that the feature will be disabled.
Although you may edit the local farm GPO by using the “LocalFarmGpo:” PSDrive, I’ll assume we’ll edit a remote farm’s GPO. Create the PSDrive:
New-PSDrive -Name FarmGpo -PSProvider CitrixGroupPolicy -Root \ -FarmGpo <server>
The <server> specified above is the name of any server in the farm that you want to modify.
The drive mapping setting is controlled per-user, so this is in the User policies:
Now you need to create a filtered policy so that we can specify the IP Range filter.
This time, you’ll notice that there are both “Settings” and “Filters” items. Let’s configure the “Filters” first.
This might be a bit hard to read, because the output is a list of objects with multiple properties each. Right now we care about the names of the filters only, so let’s view just that:
dir | select Name
You will see the five supported filter types for User policies:
- AccessControl filters the policy so that it applies only to users who connect through Access Gateway and match specific AG conditions.
- ClientIP filters the policy so that it applies only to users connecting from clients in specific IP ranges.
- ClientName filters the policy so that it applies only to users connecting from clients with specific names.
- WorkerGroup filters the policy so that it applies only when users connect to servers in specific worker groups.
- User filters the policy so that it applies only to specific users.
In our example, we care about the ClientIP filter.
Wait, what happened? The output is empty… This is because there are no ClientIP filters specified yet. Filters are added rather than set.
New-Item MyIpRange 192.168.1.0-192.168.1.255
The new filter object is created and has a few interesting properties:
- The “Mode” property can be set to Allow or Deny. When filters are evaluated within a policy, those set to “Deny” take precedence over any set to “Allow”.
- The “Enabled” property indicates whether the filter is actually active. If a filter is disabled, it will not be evaluated to determine whether the policy applies.
- The “Comment” property is free-form text and might be helpful for a filter creator to set in order to explain to the next administrator what a complex filter is actually doing.
Note that the new filter’s mode is automatically set to “Allow”. In the default mode, it is not possible to create a filter that is initially set to “Deny”, and it is not usually possible to tell when a GPO will be replicated. Therefore, be aware that during filter creation, there is a small but real chance that the GPO may be replicated with the filter set to “Allow” even if you intend to immediately switch it to “Deny”. You can prevent this from happening by using delayed writes; I’ll cover that below.
What if you need to match multiple IP ranges? We’ve got you covered. Each filter type can have any number of filter objects defined within. If any filter in a filter type matches, the filter type matches. If all filter types match, the policy is applied.
So, we can add another IP range filter:
New-Item OtherIpRange 10.0.0.0-10.255.255.255
But let’s exclude an IP range from the middle of this one:
New-Item ExcludedIpRange 10.5.0.0-10.5.255.255
Set-ItemProperty ExcludedIpRange -Name Mode --Value Deny
Note that there is no ordering required between filters, because Deny always wins.
Now that the filter is set up how we want it, we set the setting to prohibit client drive redirection. This is very similar to what we did in the previous example.
Set-ItemProperty ClientDriveRedirection -Name State -Value Prohibited
That’s it. Client drives will not be available in any session initiated from clients that match the client IP filter we specified.
The last thing I wanted to talk about today is delayed-write mode. You may have noticed that throughout working with the provider, we never told it to “Save settings”. This is because after every operation, the GPO is updated. This makes the provider work much more intuitively for an interactive user typing at the command line; you can’t forget to apply your changes. However, depending on your environment and many other factors, it might make the provider perform very slowly.
To address this problem we have built in a delayed-write mode. In this mode, changes made in the provider are not committed until you explicitly call a “Save” method. This allows you to make a whole bunch of changes and save them all at once, and results in much better overall performance of the provider. The downside is that there is no protection against a mistake where you might make many changes and then forget to call “Save”. Your changes will be lost. So be very careful when using delayed-write mode, and ensure that you always save your changes before closing your PowerShell window.
Delayed-write mode is controlled via a property on the PSDrive object that is connected to a GPO. To enable delayed-write mode, for instance on the LocalFarmGpo: drive:
(Get-PSDrive LocalFarmGpo).AutoWriteBack = $false
After doing this you can make whatever changes are necessary, and you should experience much better performance. When you are ready to save your changes:
To revert to the behavior where changes are immediately written back to the GPO:
(Get-PSDrive LocalFarmGpo).AutoWriteBack = $true
Hopefully this information will be enough to get you started with the Citrix Group Policy Provider. I may do a later, even deeper dive into the provider, but for now, happy scripting!