Tuesday, July 25, 2017

Birds Eye View of Setting up Citrix App Layering

There are some goodguides out there (such as Carl Stalhood's) that cover the details of setting up Citrix App Layering, but currently don't have a birds eye overview of what an App Layering implementation requires.

After going through many of these hurdles myself, I considered that a "light on the details" overview might be useful for the masses that are new to the tech.

This guide is written from the perspective of a VMware admin, so if you're using a cloud implementation, the details may differ in places.

  1. Download the "Citrix App Layering" package from Citrix.
  2. Import the "Enterprise Layer Manager" or ELM (this is the UniDesk VM appliance) into your Hypervisor (VMware, XenServer, or cloud whatever.)
  3. Configure the ELM network from the hypervisor console.
  4. Login to the ELM via Internet Explorer (requires SilverLight) using administrator:Unidesk1
  5. Configure the ELM
    1. Change default password
    2. Configure HTTPS Certificate
    3. Setup a connector to a network share (also setup the share, see the link to Carl's guide above)
    4. Setup Active Directory
    5. Setup Provisioning Services connector (if used) (requires an agent on the PVS server, and a reboot of the ELM after completing configuration)
  6. Create your OS Layer
    1. Create a new VM and install the desired OS
    2. Run Patches
    3. Install VM Toolstack/drivers
    4. Install/configure App Layering Package
    5. Shutdown, then Import into the ELM, creating your ELM Hypervisor connector in the process
  7. Create your Platform Layer
    1. Create platform layer from OS Layer (This creates a new VM on your hypervisor, you make the changes in that new VM)
    2. Install your VDA (Note details in Carl's guide about user accounts and groups that need recreation)
    3. If using PVS, join the domain
    4. Run ngen updates
    5. Finalize from the administrator desktop shortcut
    6. After the VM finishes shutting down, finalize the platform layer from within the ELM.(This deletes the newly created VM, and captures the changes)
  8. Create your App Layers
    1. This is largely the same as the platform layer, create, install, finalize. There are suggestions and "recipies" that can make these easier/more functional on Carl's guide.
  9. Create an "Image" from your collection of Layers
  10. Publish your "Image" to the configured connector.
  11. Throw something because you have to go back and fix a detail in a layer that you missed =)
Hopefully this helps in your planning.

Tuesday, April 12, 2016

Building custom objects in a Powershell array.

Building custom objects is almost a rite of passage for any Powershell scripter. There will be a point where you will need a multi-dimensional array, a bunch of data from disparate sources, all organized into a table that can make your pain points obvious. As this is powershell, there are a few different approaches, this is the one I use most frequently.

The Manila Folder Analogy

If you've been in an office setting, you've seen a manila folder. It's the thing that holds those important pages together. In powershell, your manila folder will be an array, declared at line 2. To put pages in the manila folder, you'll need a "PSObject". This is a Dot.Net class, so we can create it with the "New-Object" cmdlet at line 8.
Putting empty pages into a folder is a waste of time, so we need to write something on the page. This will be the properties. -Property is a parameter of New-Object, and we are going to feed it a hash-table (declared by @{}) of properties. The names of the properties are on the left of the "=" and the values are on the right.
Once the Page is constructed, you can add it to the Manila Folder, using the .add() method as seen at line 16. $thisPage is the parameter you want the method to use, so you place it in the parenthesis of the method.
#Making *SOMETHING* for it to test with
$listOfThings = 0..5

# Get/Make a ManilaFolder (List object instead of array, faster)
$manilaFolder = new-object System.Collections.Generic.List[PSObject]
foreach ($thing in $listOfThings){
  # Data Gathering?
  #Make your Page
  $thisPage = New-Object PSObject -Property @{
    #  With your data (properties)
    Property1 = "One " + $thing 
    Property2 = $thing * 2
    Title = "CustomObject Page " + $thing
    #etc, etc

# Add the Page to the Manila Folder  
You can do data gathering within the foreach, such as grabbing various pieces of data from a server, a website, an SQL box, a file, or all of these. Storing the data as you gather it into variables allows you to call those variables when when you construct the object, to fill in the properties (as observed in lines 10-12).
Updated with a suggested object type thanks to chrisdent over at powershell.slack.com 6/19/2017

Thursday, November 19, 2015

Monitoring your CDN's Webservers

(This post considers options for writing a monitor in powershell that can handle round-robin DNS load-distributed web servers, especially those that you do not control)


After observing some issues with a vendor's CDN having occasional server/service outages, I decided to write some powershell to dig through and give me a quick analysis so I could determine where the problem was and what effect it should have on the users. Some of you may read this and think "If the site is down, it's down, right?"

Well many websites now spread their resources across CDNs (Content Delivery Networks). This means that while the html page may come from one webserver (or even one CDN), the javascript and CSS file may come from another, and the images and videos from yet another. You can use the Developer Tools in your web browser to dig up out the sources for (at least THIS load) of the website.

On top of this, there are a few approaches to load balancing, where the exact same resources are duplicated across many servers, with the intent of spreading traffic around and reducing the chances of downtime. Citrix's Netscaler is a popular (and VERY capable) option, but sometimes you'll see the vendor go down the cheaper and easier path: Round-Robin DNS.


In Powershell, you can invoke the GetHostAddresses method to see how many IP addresses are linked to a DNS entry.


At the time of this writing, that returns 11 IP addresses. If you want to dig into nslookup, you can watch it cycle through the IPs in that list as you repeatedly query for google.com.

So what if your CDN is using Round Robin, and your customers are complaining of an outage, and the site appears up?

function test-CDNPage{
        ([string]$url) #Properly Formatted with http:// or https://
        write-host -ForegroundColor Cyan "Trying $url"
        Invoke-WebRequest -Uri $url
    Catch [System.Net.WebException]
    if ($_.exception -match "403")
            # 403 is expected because CDNs typically dont allow IP access
            Write-host "403" -ForegroundColor Green
            $value = $true
            # A non-403 response is treated as 
            write-host "Non-403 Response" -ForegroundColor Red
            $value = $false
    #Add Results to Object
    $thisResult = New-Object psobject -property @{
        url        = $url
        Responding = $value
    return $thisResult

$data = @()
$site = "google.com"
$ipaddys = [system.net.dns]::GetHostAddresses($site).IPAddressToString
foreach ($ip in $ipaddys){
    $data += testpage "http://$ip"

Now this code probably won't return anything because Google isn't a CDN. They see you trying to access the IP directly and re-direct your web browser to https:\\google.com. They likely also have some smarter handling behind their Round-Robin DNS solution, as they are...Google.

But consider a company like CloudFlare. They may be hosting multiple different websites from multiple different companies on their servers.

When you try to access a site like that, you load a page telling you that "direct IP access is not allowed" in your browser, and powershell's Invoke-WebRequest throws a 403 Terminating error. This is where Powershell can do things that some other monitoring tools can not. The powershell snippet above uses a try/catch statement to look at the error. If the error is a 403, it sets the $value to true. If the error is NOT a 403, it sets the $value to false. I've set this up to output an XML file every minute that is consumable by my client's monitoring system, and now they can see when individual pieces of their vendor's web services go down.

Having solid data like this can frequently get you past the first tier support layer, that at many companies is incapable of solving network issues on their own end, and is typically only a fence I must cross to reach an actual solution.

Monday, October 12, 2015

Netscaler Gateway - Configure inside/outside access rules based on AD Groups

I fought with this one for awhile. Like many others, the client had remote access configured to run through a Netscaler to a Web Interface server. And also like many others, we had to migrate this one toward Netscaler + Storefront. The detail here was that the client had two groups of users: Those allowed to work from home, and those who were not. This was controlled by AD membership in a security group.

On the older setup, the membership was checked after the netscaler, at the web-interface server. So after much thinking, more coffee, and a couple applications of forehead to desk, I determined I was going to use session policies to determine which IP address the client was coming from, and filter on AD group if the client was coming from the IP of the firewall.

I'm not covering certificate configuration, or look and feel, there are quite a few blogs that have covered these topics better then I could have done. This is about session policies and security.

Configure AAA Groups

  1. After Setting up your Gateway vServer, make sure you configure your AD Domain under the "Authentication" panel of your vserver.
  2. Navigate to "Netscaler Gateway" -> "User Administration" -> "AAA Groups".
  3. Add a new group (The name you enter here has to match your AD Security group name EXACTLY, it's case sensitive. If there was a place to use copy and paste, this is one of them.)
  4. On the next screen, click "Authorization Policies" on the right bar.

  5. Click on "No Authorization Policy"
  6. Click "Add Binding"
  7. Click the "+" Next to "Select Policy" (after the first time, you can choose the created policy for any other AD groups)
  8. Give it a name like "Auth_Policy_Allow", make sure the action is set to "Allow" and enter "ns_true" (no quotes) for the Expression, then click "Create".
  9. Click "Bind"
  10. Click "Close"
  11. Repeat Steps 3-10 above for any other AD Groups you may have.

Configure Session Profile

  1. Within your Gateway vServer, navigate to the Policies tab, Click the "+" in the upper right to add. (If the Policies tab is not shown, add it from the "Advanced Settings" pane on the right.)
  2. Select "Session" and "Request", then click "Continue".
  3. Click "Add Binding"

  4. Click the "+" to the right of "Select Policy".
  5. Click the "+" next to the "Profile" selector.
  6. Give the Profile a name. As best as I understand, the Profile controls some of the connection settings, the Policy controls when the bound profile is used. The Binding links the previous two to a vServer, in an order of application. This may help you choose your name.
  7. Click the "Security" tab.
    - Check "Default Authorization Action" and set to "Deny" (This is why we set the auth policy on the AAA groups)
    - Check "Advanced Settings"
    - Check "Authorization Groups" and move any AAA groups form the left to the right to give them access.
  8. Click the "Published Applications" tab, and configure with your storefront Web Interface Address (your settings may differ)
  9. Click "Create" to save your Session Profile.
  10. Now your profile will be set, you need to name your Policy, and set an expression. If all your external traffic comes from a single IP, say.... then your expression would be:


    If your external traffic comes from a subnet, IP range, or a few scattered IPs, you'll need to build a bigger expression.
  11. Now that your profile and policy is set, you have to give it a Priority Number. I don't claim to have a full understanding of how Netscaler Priorities work, but I do know that the Deny binding number needs to be LOWER then the Allow binding (which we still have to create). Pick your number, and choose "Bind".
  12. Now redo the above steps, for your internal traffic, BUT
    - Leave out the security config (step 7)
    - For the policy expression (step 10)  enter:  ns_true
    - For the binding priority, go one lower (if you picked 100, go with 99
At this point, you should have a Gateway vServer that allows logins for all internally, but requires your AD membership when coming from outside addresses. If you need to test without actually going outside your network, you can use a different expression to treat your admin subnet or IP address as "outside". This expression allows you to filter on a subnet instead, just make sure you don't lock out anyone important :D

REQ.IP.SOURCEIP == -netmask     

Wednesday, September 30, 2015

StoreFront 3 - Customize the Placeholder text on the username field, and a simple CSS background.

PlaceHolder Text

Many of us are making adjustments to a new StoreFront 3 implementation. I found that in particular circumstances, the placeholder text of "domain\user or domain@user.com" would just be confusing. This is due to the client having only one trusted domain. Username alone is sufficient there. I dug around in Javascript and jQuery for a bit, and came up with a script that works great in web and Mac receivers, but breaks the Windows Native Receiver due to it not dealing well with looping scripts.

Asked on the Citrix boards, and I got this response:
1) Navigate to the resources folder for the Authentication Service, typically C:\inetpub\wwwroot\Citrix\Authentication\App_Data\resources
2) Open ExplicitFormsCommon..resx, e.g. ExplicitFormsCommon.en.resx in a text editor
3) Search for DomainUserAssistiveText
4) Adjust the value and save.
5) Restart IIS

CSS Background

I was also looking at that vast white space around the desktop and app icons in receiver, and didn't care for the void. So I brewed up a stylish background for the "storeViewSection". It displays some blueish tie stripes at an angle behind the icons, all in CSS. Drop this into the "/Citrix/StoreNameWeb/custom/custom.css" to use, and tweak as you need.
    /* Blue stripe*/
   #f0f0ff 3px,
    /*White Stripe*/
   #ffffff 3px,
   #ffffff 6px,
    /*Light Stripe*/
   #f6f6ff 6px,
   #f6f6ff 56px,
    /*White Stripe*/
   #ffffff 56px,
   #ffffff 59px,
    /*Blue Stripe*/
   #f0f0ff 59px,
   #f0f0ff 62px,
    /*White Space*/
   #ffffff 62px,
   #ffffff 124px

Monday, May 18, 2015

Custom Event Logs

Creating a new EventLog in powershell

Create the new log:
New-EventLog -LogName MyEventLogName -Source scripts
See that it exists:
Get-EventLog -List
Write to it:
Write-EventLog -LogName MyEventLogName -Source scripts -Message "Dude, it works ... COOL!" -EventId 0 -EntryType information
And then pull the events from the log:
Get-EventLog -LogName MyEventLogName
Eventlogs created in this manner will show under the "Applications and Services Logs" branch in the Event Viewer (which can be launched with "show-eventlog"). You can use either the GUI, or the "Limit-EventLog" cmdlet to make changes to the size and retention options for the log (shown with get-eventlog -list)

Custom Permissions on Eventlogs

In Windows 2008 the SDDL is set with the "wevtutil" app.
To begin, we need to get the current SDDL string. It's the "ChannelAccess" line in the output of the following command:
wevtutil gl MyEventLogName
Copy the channelAccess: line into the text editor of your choice:

channelAccess: O:BAG:SYD:(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x5;;;SO)(A;;0x1;;;IU)(A;;0x1;;;AU)(A;;0x1;;;SU)(A;;0x1;;;S-1-5-3)(A;;0x2;;;LS)(A;;0x2;;;NS)(A;;0x2;;;S-1-5-33)(A;;0x1;;;S-1-5-32-573) )

Each of the (A;;xxx;;;x-x-x-xx) strings is a SDDL permission, the first "xxx" sets the permission, according to this:
Clear 0x4
Read 0x1
Write 0x2

The second "x-x-x-xx" is a user or group SID. Depending on the SID's source, it may be longer (for instance if it's coming from active directory). There is a list of "well-known" SIDs here. Build a new SDDL permission, and tag it onto the end of the existing string.

O:BAG:SYD:(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x5;;;SO)(A;;0x1;;;IU)(A;;0x1;;;AU)(A;;0x1;;;SU)(A;;0x1;;;S-1-5-3)(A;;0x2;;;LS)(A;;0x2;;;NS)(A;;0x2;;;S-1-5-33)(A;;0x1;;;S-1-5-32-573) (A;;0x1;;; S-1-5-3-3127463467463))

Finally, we need to apply the new SDDL. Paste your modified SDDL String (complete with the O:BAG:SYD on the front) after the /ca:
wevtutil sl MyEventLogName /ca:
Attempting to set the SDDL from powershell will cause a mess of syntax errors, due to the powershell interpreter trying to parse the SDDL string. This may be avoidable with single-quotes, but I just ran "cmd", re-ran the command, and then ran "exit" to return to powershell.
Now with a new eventlog with custom permissions, you can use write-eventlog to write output from your powershell scripts.

Jane Lewis Blog
Hey Scripting Guy Blog

Friday, January 30, 2015

Confluence User Macro - Using a rest interface with jquery and javascript

While implementing Confluence, I took note of the Jira ticket integration. Upon looking at WebHelpDesk, I found an available rest interface. However, WebHelpDesk does not include the CORS compliant responses in it's response packets, so browsers won't allow cross site-scripting. Using a netscaler or other similar solution, one could make the requests through such a means and have the response packets add the CORS compliant header.

With that hurdle out of the way, it was simply a matter of parsing the REST response using jquery, and building the HTML. Using the existing CSS references, and a custom icon, I built a macro that takes a ticket number in confluence, and builds it like a Jira ticket link.

## Macro title: WHD Ticket Macro
## Macro has a body: Y or N
## Body processing: Selected body processing option
## Output: Selected output option
## Developed by: Jared Shippy
## Date created: 01/30/2015
## Installed by: My Name

## @param Ticket:title=TicketNumber|type=string|required=true|desc=WebHelpDesk Ticket Number