So, in the recent past, I had a need to build out a new ISA array for a special project. Now, you might be thinking at this point ‘OK, sure, but why PowerShell? Isn’t that overkill?’ and you would be right under normal circumstances. The thing that made this particular job a tad more painful was the need to generate 50 network definitions and access rules in order to accommodate the specific use to which ISA would be put. Now, I don’t know about you, but the need to generate 50 rules, each with minor yet predictable variations, just screams script.

So now maybe you are asking ‘But why reinvent the wheel since there are tons of examples out there using VBScript that do pretty much the same thing right?’ and the answer is…yes and no. There are examples out there for creating networks and creating rules both (see http://www.isascripts.org for some really great examples), but I needed to generate 50 of them and I didn’t necessarily want to generate a script, I just wanted to DO it. In cases like this especially, even when working with COM objects, PoSh is still the best tool for the job…or so my thinking went. I quickly discovered that, as great as ISA is as a product, its COM interface could use some work.

So we’ll start this journey at the beginning by initializing the COM object in PoSh, which can be done as so:

PS C:\>$root = New-Object –com "FPC.Root" –Strict

Now you will notice that I used the –Strict switch on my object. Running the cmdlet through the help indicates that this is done to cause an alert to be raised when the COM object uses an interop assembly so that you can more readily distinguish between actual COM objects and COM-callable wrappers for .NET objects. The reasons for needing this distinction in this case are not entirely clear to me, not being a programmer, but this appears to be the preferred method in all examples I have found, so we’ll go with it.

Next thing we need to do is set up bindings for a couple of the elements that we will be working with, namely networks, rule elements, and rules within an array. If we pass our $root object to Get-Member, we can see that the best method to use is likely going to be the ‘GetContainingArray’ method like so:

PS C:\>$array = $root.GetContainingArray()

This works fine when you have only a single array, but I don’t think this will bind properly to what we need if you have multiple arrays. In that case, it might be better to bind in a slightly different manner using the name of the array like so:

PS C:\>$array = $root.arrays | Where-Object {$_.name –like ‘MyArray’}

My preference in this instance will be to do a ‘Do-Until’ loop, so we need a simple counter place holder that we will go ahead and start at 1:

PS C:\>i = 1

Don’t forget that, before every new Do – Until loop that uses $i, you will first need to set $i back to equal 1 before starting your loop. Otherwise, you will not loop since the value of $i would still be greater than 50 and would stop. Alternately you could probably use different variables for each loop, but I think it’s just as easy the other way.

Next we want to create our networks. If we pass $networks to Get-Member, we quickly see that there is no obvious method for ‘Adding’ a new network. This is what I was referring to earlier on the COM interface needing a bit of work. In my mind, a properly built COM object would provide what you need via Get-Member even if it is an inherited item from a parent. Unfortunately, even checking the parent objects doesn’t reveal a method for adding a new network, so how do we do it?

For this I had to resort to downloading and combing through the ISA SDK. True, this is available online via MSDN, but it’s much easier to search for what I am looking for with the CHM format. In addition to this, not all of the samples are available in full format online so that is another reason to have the SDK installed since these examples are then installed locally.

After reviewing the CHM, it turns out that the majority of COM interfaces on ISA have an ‘Add’ method even when it isn’t otherwise reflected via Get-Member. Why this is the case is beyond me, but we’ll roll with it. When calling the ‘Add’ method for the COM objects associated with ISA, it always returns an instance of the new object for further manipulation. One of the nice aspects of this is that none of the changes are committed until you call the ‘Save’ method. This is helpful because we need to specify all the settings we want for our new network before we commit (not that we have commitment issues, we just like to work up to it gently). Since an object is returned, we will want to bind it to a variable so we can work with it which would look something like this:

PS C:\>$net = $networks.Add(("MyNetwork" + $i))

Now we have the variable $net bound to our new network which is called MyNetwork plus whatever the current value of ‘i’ is at the time. You may have noticed that I used double parenthesis (if you didn’t its ok, I don’t expect you to know everything this time…but next time there will be a test). The reason for this is a matter of execution priorities within PoSh. I want it to figure out what ‘"MyNetwork" + i’ is before it executes the ‘Add’ method since the method likely lacks the processing instructions to do it after. Methods are simple…they want what they want when they want it and only the way they want it (kinda like my two year old) so we don’t want to startle or scare our little method by asking it to do odd things. Thus, we do the processing first and provide only the results. I could have done this outside of the method call, but then I would have to assign it to a variable and increase the number of lines in my script. Doing it this way instead has the same effect, but requires less effort in my opinion.

So now we have a new object called $net. We can pass this to Get-Member, and it provides us with a handy little list of the remaining properties to be set. Each item that we care about needs to be set before we commit the changes like so:

PS C:\>$net.AutoDiscoveryPort = "80"

PS C:\>$net.Description = ("Test isolation network" + $i)

PS C:\>$net.EnableAutoDiscoveryPort = $true

PS C:\>$net.EnableFirewallClients = $true

PS C:\>$net.EnableWebProxyClients = $true

PS C:\>$net.IpRangeSet.IP_From = ("192.168." + $i + ".1")

PS C:\>$net.IpRangeSet.IP_To = ("192.168." + $i + ".50")

PS C:\>$net.LDT = "mydomain.com"

 

Of course, if you ran $net through Get-Member, you will probably notice that I didn’t specify every property and that some items even have several sub elements. As to why some and not others, some items have a default setting that was already what I needed while others required changing and yet others cannot be set. Items that cannot be set are usually obvious via the output of Get-Member because they indicate only {get} and not {set} which indicates they are read only properties. This could be because the property is managed by the system, or it could be because these properties actually contain ‘enumeration collections’ which are themselves objects and require you to set the sub objects rather than the property itself. This is the case with the ‘IpRangeSet’ property. Get-Member showed this property with only the ability to {get} and so running $net.IpRangeSet through Get-Member showed me the sub object properties that needed to be set. This is also evident in the SDK help file as the property is shown as read only, but derived from a sub object whose properties you can set. Others might indicate that the property is completely read only without a reference to another object and these you just have to accept the defaults for. In my case, the above properties were all I needed to set, but you might need others so I would strongly suggest grabbing the SDK from the Microsoft downloads site if you think you need other properties (though you can always sift through each property using Get-Member as well…but my way is shorter…trust me). The last thing I need to do is to save my changes like so:

PS C:\>$net.Save()

It’s important to note that, while this network is available now to ISA to use in the creation of rules and the like, it isn’t actually active yet. If you have worked with ISA for any length of time, you will probably realize that this is because we haven’t applied the changes yet (and no, save is NOT the same as apply).

So, all of the above goes into a Do – Until loop, of course with an increment on each loop for $i making this whole portion of the script look like this:

PS C:\>Do {

> $ net = $networks.Add(("MyNetwork" + $i))

> $net.AutoDiscoveryPort = "80"

> $net.Description = ("Test isolation network" + $i)

> $net.EnableAutoDiscoveryPort = $true

> $net.EnableFirewallClients = $true

> $net.EnableWebProxyClients = $true

> $net.IpRangeSet.IP_From = ("192.168." + $i + ".1")

> $net.IpRangeSet.IP_To = ("192.168." + $i + ".50")

> $net.LDT = "mydomain.com"

> $net.Save()

> $i ++

> } Until ($i –gt 50)

 

The next part, of course, is to define the network set and routing rules for these networks. Rather than loop this though, we can simply make them the next logical step in our script like so:

PS C:\> $netset = $array.NetworkConfiguration.NetworkSets.Add("All Test Iso Networks")

PS C:\> $netset.Networks.Add("External", 0)

PS C:\> $netset.Networks.Add("Internal", 0)

PS C:\> $netset.Networks.Add("Local Host", 0)

PS C:\> $netset.Networks.Add("Quarantined VPN Clients", 0)

PS C:\> $netset.Networks.Add("VPN Clients", 0)

PS C:\> $netset.Save()

 

You may have noticed that we didn’t add any of the MyNetwork networks we created before. We could have done this via another Do – Until loop, but we don’t have to go to all that trouble. Instead, we use the all-inclusive option for creating a network set and specify only the networks we want Excluded from this set. We do this by calling the ‘Add’ method (since we are referencing an existing object) and specifying the value for the Name property that represents the network we wish to exclude, and then setting either a 0 to exclude or a 1 to include the network. Since the only networks that exist on this ISA array other than the defaults are the ones I created, this represents a much smaller list than adding in all 50 of my networks I created.

Next, we have to define the routing relationship between this network and other networks. We do this almost the same way we did with the NetworkSet as follows:

PS C:\> $netrule = $array.NetworkConfiguration.NetworkRules.Add("MyNetwork to Internal")

PS C:\> $netrule.DestinationSelectionIPs.Networks.Add("Internal", 1)

PS C:\> $netrule.DestinationSelectionIPs.Networks.Add("VPN Clients", 1)

PS C:\> $netrule.SourceSelectionIPs.Networks.Add("All Test Iso Networks", 1)

PS C:\> $netrule.RoutingType = 0

PS C:\> $netrule.Save()

 

Since we already had the network set available, we simply added it to the source and our other ‘Internal’ networks to the destination. These network rules cover traffic in both directions, so there is no need to create any additional rules for traffic headed the other way. The only other item we set was the RoutingType, which controls whether the network uses a NAT or Route style when navigating traffic. Setting a value of ‘0’ sets the type to Route while a value of ‘1’ indicates NAT. The way I figured this out was to review the FpcCfg.vbs available in the ‘inc’ directory where you have installed the SDK for ISA. It provides value options for just about every item you might need to set. This was cross referenced with the information provided in the help file which indicates only that a value of either fpcRoute (0) or fpcNAT (1) needs to be set. I searched the vbs for those values to find out what they translated to.

The next piece, in my case, was to populate user sets that were tied to groups in AD. This was so that I could restrict access to each network to only those people who had been granted access. This process is fairly simple as well…except for the binding to the right AD user part which is slightly annoying. In my case, I had pre-created 50 AD groups with the same name, but incremented numbers on the end just to keep things consistent. I did that with PoSh as well using the Quest AD cmdlets, but will not cover that step in this post. The general process for adding each user set looks like this:

PS C:\> $uset = $array.RuleElements.UserSets.Add(("MyNetwork" + $i))

PS C:\> $uset.Accounts.Add(("mydomain\MyNetworkGrp" + $i))

PS C:\> $uset.Save()

 

As before, you will want to stick that into a loop like we did with the creation of the networks themselves. You could be asking yourself why we didn’t go ahead and create all of this inside of one loop at this point. Some things, such as the networks and usersets, we probably actually could, but others, like the Access Rules themselves and the network set, require the creation of the other elements first.

So now we are up to the next to last part, which is creating the Access Rules to allow traffic to actually flow between the networks. You see, it isn’t enough to simply specify a network has a routed or NATed relationship, you also have to specify what traffic is allowed to flow for travel between any two networks unless they use the same interface on ISA. In my case, it was fairly simple as all I wanted to do was allow inbound access from the internal networks to the desired MyNetwork based upon being in the correct AD group. You will also likely want to create a rule that allows your individual networks outside for web browsing or whatever, but I will not demonstrate that here on this post since the general process will be covered by the add rules loop I am about to show you. The only difference is that you will add the Network set instead of an individual network and the ‘All Users’ user set instead of a specific AD group user set. The process for creating the new access rule is as follows:

PS C:\> $accrule = $array.ArrayPolicy.PolicyRules.Add(("Int to MyNetwork" + $i))

PS C:\> $accrule.SourceSelectionIPs.Networks.Add("Internal", 0)

PS C:\> $accrule.AccessProperties.DestinationSelectionIPs.Networks.Add(("MyNetwork" + $i), 0)

PS C:\> $accrule.AccessProperties.UserSets.Add(("MyNetwork" + $i), 0)

PS C:\> $accrule.Action = 0

PS C:\> $accrule.Save()

PS C:\> $i ++

 

As before, the above will be enclosed in a Do – Until loop just like the first one. As before, I only set those properties that I felt needed to be set for my purposes and there are still many more that could be set. As shown, the above will generate an access rule that allows unrestricted access on any protocol for users on the internal network who are a member of the correct group for the chosen destination network.

The very last step is to commit all our changes and additions to ISA to make them live which we do like this:

PS C:\> $root.ApplyChanges()

In this instance, we have to commit or dump these changes at the root level because we are accessing an array. If you are using ISA standard, you could actually do this using the $array instead of $root I believe.

Anyway, I hope this post proves useful to someone and, as always, please feel free to reply with your suggestions for improvements or questions if you are stuck.

Merddyn

Advertisements