RDS-O-Matic for Windows. It is ready!

It is show time people. Just want to show you the native RDS-O-Matic Win32 app. I had time to port if from the Excel spreadsheet idea and it is not only small but works like a champ.

I know Alex (Twitter @E2EVC) will be complaining about its GUI but hey, for a tool that is this simple there is no need for a fancy GUI. People want apps that work and are simple to use. That is the bottom line and this is what you get with RDS-O-Matic.

So here you have it, on all its glory.

Just want to remind you guys one more time that RDS-O-Matic will be officially released at PubForum (E2EVC) Dublin, in June 2016.

Cheers.

CR

 

14,329 total views, 2 views today

RDS-O-Matic live in the wild.

Ladies/Gentlemen,

Just a quick post to show you RDS-O-Matic in action. I just updated it to match what we have on the book I am releasing with Freek Berson. As I mentioned before this little handy Excel spreadsheet (Windows app will be ready and released at E2EVC Dublin) right now does the following:

  • Creates a complete RDS deployment with:
    • 2 RD Connection Brokers
    • 2 RD Web Access servers, load balanced using NLB on port TCP 443.
    • 2 RD Gateway servers, load balanced using NLB on ports TCP 443 and UDP 3391.
    • 2 RD Session Host servers with the desktop experience loaded.
    • 2 RD Licensing Servers, activated and set to whatever you choose (Per User/Device).
  • Creates a collection with the 2 RD Session Hosts on it.
  • Creates a test published application, WordPad, so you can try the environment immediately.
  • Enables HA on the RD Connection Brokers. For that you need to have SQL ready to go, the folder where the database will be stored has to be created and permissions for the RD Connection Brokers set. This is all detailed and explained in the book. We even cover the actual SQL install.
  • Retrieves the certificate (you enter the location for the certificate in PFX format) and deploys it to all roles. Here I assume you will be using a single, wildcard certificate for all the roles. If not the case, you can easily change the script you get to use different certificates.
  • Final warning, you must have the output folder created before you hit the ‘RDS-O-Matic’ button (i.e. C:\RDSOMatic).

Now that I am back at developing for iOS, is there any value in creating an iOS app that you can enter all this info and it will email you the script ready to be deployed?

Let me know.

Cheers.

CR

9,346 total views, 1 views today

PVS Retries – Script

As PVS retries can indeed cause all sorts of degradation to the user experience (i.e. applications freezing or overall slowness) and it is not something that is readily exposed on any of the Citrix monitoring/management consoles (even the PVS console does not show that info, or Director for that matter), I decided to write this little PowerShell script to get that information and show it in a nice graph. This is what it looks like:

PVSGraph

Couple comments:

  • What is considered high/normal/low for retries? I have no idea if anyone ever came up to a number. Also keep in mind the number returned by PVS is since the machine booted up or since someone reset the counter. So 1000 retries over 10 days is not a big deal if you ask me but 1000 in 5 minutes there is indeed something wrong. I would love to hear what others have to say.
  • I could (and should ) calculate and show Retries/min instead of just retries. Simply a matter of retrieving the uptime of the server, converting to minutes and dividing retries by that.
  • I assume you know how to get the MS Chart .NET libraries/PVS stuff registered/working.

So here is the code:

# This function was created by Remko, another Citrix CTP
# and probably the craziest motherfucker I have ever met.
# As the PVS PowerShell sucks, not even returning proper objects
# people like Remko took matters on their own hands.
# You can see his post here.
# http://www.remkoweijnen.nl/blog/2012/02/29/convert-mcli-output-into-powershell-objects/

function ToObject {
    param(
     [Parameter(
          Position=0,
          Mandatory=$false,
          ValueFromPipeline=$true,
          ValueFromPipelineByPropertyName=$true)
    ]
    [Alias('Command')]
    [string]$cmd
    )
 
     $collection = @()
     $item = $null
 
     switch -regex (Invoke-Expression $cmd)
     {
          "^Record\s#\d+$"
          {
                if ($item) {$collection += $item}
                $item = New-Object System.Object
          }
          "^(?<name>\w+):\s(?<value>.*)"
          {
                if ($Matches.Name -ne "Executing")
                {
                     $item | Add-Member -Type NoteProperty -Name $Matches.Name -Value $Matches.Value
                }
          }
     }
     return $collection
}


# Loads the appropriate assemblies
[void][Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)
[void][Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms.DataVisualization”)
Add-PSSnapin –Name McliPSSnapIn -ErrorAction SilentlyContinue
Mcli-Run SetupConnection -p server="ENTER YOUR PVS SERVER FQDN HERE (i.e. PVS01.Company.com)"
$XAServers = 'Mcli-Get DeviceInfo -p siteName="YOUR PVS SITE NAME",collectionName="DEVICE COLLECTION YOUR VMs ARE IN"' | ToObject

# Creates chart object
 $Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart
 $Chart.Width = 1000
 $Chart.Height = 600
 $Chart.Left = 10
 $Chart.Top = 10

# Creates a chartarea to draw on and add to chart
 $ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
 $ChartArea.AxisX.Interval = 1
 $ChartArea.AxisX.Title = “Servers”
 $ChartArea.AxisY.Interval = 50
 $ChartArea.AxisY.Title = “PVS Retries”
 $Chart.ChartAreas.Add($ChartArea)
 [void]$Chart.Series.Add(“Data”)
 $Chart.Series["Data"]["DrawingStyle"] = "Cylinder"

# Adds a data point for each server
 foreach ($server in $XAServers)
 {
 
 $dp1 = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $server.status)
 
 # For my particular needs I assumed the retries as this:
 # Good, under 100. Attention, between 100 and 300. Bad, over 300.
 # Am I right? No clue. Please comment/contribute with your findings.
 
 If ([int]$server.status -lt 101) 
    {
     $dp1.Color = [System.Drawing.Color]::Green
    }
   Else
    {
     If ([int]$server.status -gt 100 -and [int]$server.status -lt 301)
        {
         $dp1.Color = [System.Drawing.Color]::Yellow
        }
       Else
        {
         $dp1.Color = [System.Drawing.Color]::Red
        }
    }

 $xlabel = $server.deviceName
 $dp1.AxisLabel = $xlabel
 $Chart.Series[“Data”].Points.Add($dp1)
 }
 # Sets the title to the date and time
 $title = new-object System.Windows.Forms.DataVisualization.Charting.Title
 $Chart.Titles.Add( $title )
 $Chart.Titles[0].Text = date

# Saves the chart to a file on the server where the script runs.
# Could be anywhere, even UNC path.
 $Chart.SaveImage(“C:\Graph\FarmRetries.png“,”png”)

It can be certainly improved and I will work on that. For now, give it a try and let me know what you think.

CR

6,582 total views, no views today

Disappearing Citrix Policies – Scripting a fix

Not sure how many of you have experienced Citrix Policies disappearing from the AppCenter console, on XenApp 6.5. I know it is an issue as many posted similar occurrences in the Citrix Support Forums.

Not sure if it is a problem with XenApp/XenDesktop 7.6 on the FMA architecture as the majority of my large customers (talking about 10,000+ users concurrently) are still on XenApp 6.5 given the fact it was for sure the most stable/robust release ever (ok, after all the Feature Packs/Hotfixes) and the one that had the most features on the Platinum license.

Back to the issue, in certain cases the fix was to restore the database. Yep. Simply updating the Group Policy modules to the latest 1.7.X releases does not necessarily fix it.

So here you have a quick and small PowerShell script that I wrote and scheduled on the Controllers so they backup the policies to a folder named after the date they run (so you can keep backups of as many versions as you want, in case you need them). Dirty and simple.

Here you have it (it assumes the XenApp SDK is already loaded – the PSM1 module you can download off http://support.citrix.com/article/CTX128625):

Add-PSSnapIn Citrix.* -ErrorAction SilentlyContinue
Import-Module C:\Scripts\Citrix.GroupPolicy.Commands.psm1
$date = Get-Date -format yyyy-MM-dd
$Destination = “C:\CitrixPolicies\” + “$date”
Export-CtxGroupPolicy $Destination

The date format is case sensitive (If you use mm you get the minutes when the script ran and not the month. Months are MM – do not ask me how I know that).

It does the job.

CR

 

2,010 total views, no views today

Citrix PVS Image Copy

If you built your Citrix environment properly, you should have by now at least a test environment and a production one. And if PVS is part of your deployment, the same applies to it. A development PVS and a production one.

If you do not see why you would need a test environment, separated from you production one, please stop here. This article is not for you. For sure.

That said one of the tasks I usually have to deal is to move images from a particular PVS environment to another one. As mentioned previously this usually has to do with moving something from a test/development environment to production, once it is deemed ‘good-to-go’.

To make my life easier I wrote a simple script that takes a PVS image from a particular environment/store and copies it to another one. It takes care of exporting, copying and importing the vDisk for you. Simple but effective.

Here you have it:

 

=== BEGIN ===

#
# Copies a vDisk between PVS environments.
# Cláudio Rodrigues 2014-12-24 V1.0
#

<#
.SYNOPSIS
CopyvDisk 1.0
IQBridge Inc., 2014. All Rights Reserved.
.DESCRIPTION
PowerShell script to move a vDisk from a PVS Farm to another one.
.PARAMETER vDiskName
The name of the vDisk you want to copy.
.PARAMETER SourceEnv
The PVS Environment where your vDisk is currently used.
.PARAMETER SourceStore
The PVS Store where the vDisk you want to copy is located.
.PARAMETER DestEnv
The PVS Environment that will use the vDisk.
.PARAMETER DestStore
The PVS Store where the vDisk you want to copy will be saved.
.EXAMPLE
C:\PS>
.\CopyvDisk.ps1 XenApp65V2 DEV Development PROD Production
Copies the vDisk XenApp65V2 from the DEV environment, out of the Development store
to the Production Store in PRD.

.NOTES
Author: Cláudio Rodrigues
Date:   December 24, 2014
#>

Param(
[Parameter(Mandatory=$True, HelpMessage=”The vDisk to be copied”)]$vDiskName,
[Parameter(Mandatory=$True, HelpMessage=”Source PVS Environment”)]$SourceEnv,
[Parameter(Mandatory=$True, HelpMessage=”Store where the vDisk resides”)]$SourceStore,
[Parameter(Mandatory=$True, HelpMessage=”Destination PVS Environment”)]$DestEnv,
[Parameter(Mandatory=$True, HelpMessage=”Store the vDisk will be copied to”)]$DestStore
)

Switch ($SourceEnv)
{
PROD { $SourceServer = “prodpvs.yourcompany.com” }
DEV  { $SourceServer = “devpvs.yourcompany.com” }
}

Switch ($DestEnv)
{
PROD { $DestServer = “prodpvs.yourcompany.com” }
DEV  { $DestServer = “devpvs.yourcompany.com” }
}

Add-PSSnapin –Name McliPSSnapIn -ErrorAction SilentlyContinue
Mcli-Run SetupConnection -p server=$SourceServer

$TempPath = Mcli-Get Store -p storeName=$SourceStore -f path
$SourcePath = $TempPath[4].SubString(6)
Mcli-Run ExportDisk -p diskLocatorName=$vDiskName, siteName=YOUR_SITE_NAME, storeName=$SourceStore
Mcli-Run SetupConnection -p server=$DestServer

$TempPath = Mcli-Get Store -p storeName=$DestStore -f path
$DestPath = $TempPath[4].SubString(6)

c:\windows\system32\robocopy $SourcePath $DestPath “$vDiskName.*” /MIR /xo /XF *.lok /XD WriteCache

Mcli-RunWithReturn ImportDisk -p diskLocatorName=$vDiskName, siteName=YOUR_SITE_NAME, storeName=$DestStore

Mcli-Run UnloadConnection

=== END ===

This is what you will need to change:
– If you have multiple environments (i.e. Development, Test, Pre-Production, Production, etc) you will need to add all of them by their name/code and the PVS server that is part of the environment. This is done where you see the ‘Switch’ statement. In this example I have two environments, named PROD and DEV and each one has its own PVS server.
– The site name. Replace YOUR_SITE_NAME with the correct name for your PVS Site. This script assumes the Site Name is the same across all environments (I see no reason for it to be different – if you have a reason please let us know in the comments).

The script takes five (5) parameters:
– vDisk name: the name you have for the vDisk on the PVS console, like XenApp65-v1.
– The source environment: this has to match one of the names/codes you added to the ‘Switch ($SourceEnv)’ line. In this example I created one called PROD where the PVS Server for that is prodpvs.yourcompany.com and another one called DEV (with devpvs.yourcompany.com as the PVS Server). You can name these anything you want. I used PROD and DEV as these make sense to me.
– The source store: under PVS you have your stores where the vDisks reside. Here you pass the store where the vDisk you want to copy is.
– The target environment: to which environment (as explained under source environment) the vDisk will be copied to.
– The target store: under which store on the target PVS environment you want the vDisk to be copied to.

Couple comments:

– You must make sure a vDisk with the same name does not exist on the target store. Otherwise it will fail. Yes, I am lazy and I could have added logic to the script to check for that and copy it somewhere else (or delete it) before doing the copy/import. I did not do it. Yes, because I am lazy and today is Christmas Eve.
– There is not much error checking on the script as the script assumes you know what you are doing and if things are passed properly it works flawlessly. So yes, I do not save your ass if you do not know shit. Keep that in mind.
– Of course the images have to be environment agnostic (meaning the database/farm settings will be dumped by GPO to allow you moving PVS images anywhere).
– The images have to be part of the same domain right?

Other than that a very simple script that has helped many of my customers over the years!

Time to celebrate Christmas.

Cheers.

CR

5,222 total views, 6 views today

XenApp Load Script

This is another post on XenApp 6.5 scripting. And yes, again, the reason for that is a ton of people are still on XenApp 6.5 and not everyone has a budget for all the fancy and pretty monitoring tools out there. Not saying they are not good. They are great. But money talks at the end and the economy is not that great for many XenApp customers out there so cheapo is the way to go sometimes.

Based on the work of others (my apologies but honestly I cannot find where this came from originally – if the author contacts me, all credits for the initial script will be given here), I tweaked this script to get me the load on a particular worker group, in an easy to read graph. Take a look at it:

XenApp Farm Load

I agree it is not the fanciest graph out there but allows you to see all servers in the farm and how much load you have on each of them. You can then add to a web page that will refresh itself every 60 seconds for example so you always have the latest and greatest data from the farm. So here you have the script:

=== BEGIN

# load the appropriate assemblies
[void][Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)
[void][Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms.DataVisualization”)
Add-PSSnapin Citrix* -ErrorAction SilentlyContinue

# get the server names and load
$XALoad = Get-XAServer -WorkerGroup YourWorkerGroup | Get-XAServerLoad | Select-Object ServerName,Load

$XALoad = $XALoad.GetEnumerator() | sort ServerName

# create chart object
$Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart
$Chart.Width = 1000
$Chart.Height = 600
$Chart.Left = 10
$Chart.Top = 10

# create a chartarea to draw on and add to chart
$ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
$ChartArea.AxisX.Interval = 1
$ChartArea.AxisX.Title = “PROD”
$ChartArea.AxisY.Interval = 1000
$ChartArea.AxisY.Title = “Load”
$Chart.ChartAreas.Add($ChartArea)
[void]$Chart.Series.Add(“Data”)

# add a data point for each server
foreach ($server in $XALoad)
{
$dp1 = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $server.Load)
$xlabel = $server.ServerName
$dp1.AxisLabel = $xlabel.Substring(3)
$Chart.Series[“Data”].Points.Add($dp1)
}
# set the title to the date and time
$title = new-object System.Windows.Forms.DataVisualization.Charting.Title
$Chart.Titles.Add( $title )
$Chart.Titles[0].Text = date

# save the chart to a file
$Chart.SaveImage(“C:\Graph\XenAppFarm.png“,”png”)

=== END

Make sure you change the Worker Group name and the location where you want the graph saved (it is in PNG format and the folder must exist). In my case I run the script on the XenApp controller.

There is still some stuff I want to add like:

– Different colours based on the load (i.e. red if over 8000, green if under 5000, etc).
– As each server can have up to 9999 as its load index before being unavailable (10000) if you know the number of servers you have you can indeed show a load percentage for the whole farm. Sure it is not a perfect metric but it is a good indicator. For example if you have 10 servers and the load on all of them adds up to 84350 we can be pretty sure you are at around 84.4% capacity on your farm. Again this is not an exact number but a pretty good idea where things stand when looking at the graph.

The idea here is to create a bunch of these graphing scripts and add all to a single ‘dashboard’ page that will show you the most relevant quick information you need when monitoring a XenApp environment.

Contributions to this in terms of scripts and new ideas are more than welcome.

Cheers.

CR

 

2,820 total views, no views today

XenApp Reboot Script

As many customers are still running XenApp 6.5, probably one of the most stable/successful XenApp releases, when time comes to reboot the servers on the farm the options available are quite limited.

After looking for some scripts I ended up finding the one fellow CTP Dane Young wrote and posted on his site. The problem IMHO it is overkill for many customers (what ends up making it complex, especially for people not very familiar with PowerShell).

I took his script as a starting point and created a much simpler version that works very well and does what most administrators need:

– Reboots the farm in groups of servers.
– Prevents new logons to the servers that will be rebooted.
– Sends messages to the users 15, 10 and 5 minutes before the reboot will happen.
– Reboots the servers, not waiting for the servers to be completely drained. To be honest I prefer this approach as if you need to wait until a server has no more users you may have to wait days in certain cases.
– Every interaction is logged to the event log (disabling logons, sending messages to users, servers being rebooted, etc).

In this example, the need was to reboot the farm in two passes. One covering half of the servers and another for the remaining servers, 30 minutes later. For this particular case the farm had to be rebooted twice a week. So this is what we did, preparation wise:

– Created four worker groups named TuesdaysRebootGroup1, TuesdaysRebootGroup2, FridaysRebootGroup1 and FridaysRebootGroup2.
– Added half of the servers to TuesdaysRebootGroup1 and the remaining servers to TuesdaysRebootGroup2. Did the exact same thing for the FridaysRebootGroup1 and FridaysRebootGroup2 worker groups.
– On the ZDCs (two of them), we created two scheduled tasks on each of them. On the first ZDC the two tasks are scheduled to run on Tuesdays and Fridays at 1:00am and on the second ZDC the tasks run on Tuesdays and Fridays at 1:30am. The tasks on the first ZDC take care of rebooting the servers on Group1 and the ones on the second ZDC take care of rebooting the servers on Group2.

Here you have the script:

#### BEGINNING

#———————————————————————————–
#———————————————————————————–
# Reboot script for XenApp 6.5 Citrix Farms
# Simply disables new logons and issues warning messages every 5 minutes
# for 15 minutes in total.
# This script can be run as a schedule task from the Zone Data Collector to process
# reboots for all other application servers
# Created by Cláudio Rodrigues, Citrix CTP, Microsoft MVP, VMware vExpert
# WTSLabs Inc. Copyright 2010, 2011, 2012, 2013
# http://blog.wtslabs.com
# Loosely based on the work by Dane Young, Citrix CTP
# Check http://blog.itvce.com/?p=79 for more information
# Build 2014.11.24 Revision 6
#———————————————————————————–
#———————————————————————————–

Add-PSSnapin “Citrix.Xenapp.Commands” -ErrorAction SilentlyContinue

# Define which worker group should be processed.
# We are using one script per worker group so make copies and change the worker groups as needed.
# Can be easily modified to allow multiple worker groups (see Dane’s script)
$Global:WORKERGROUP = “TestCR”
$Global:EventLog = New-Object -type System.Diagnostics.Eventlog -argumentlist Application
$Global:EventLog.Source = “Citrix Reboot Script”
$EventLog.WriteEntry(“Starting scheduled task Citrix Reboot Script.”,”Information”,”111″) # Create test event entry to note the start time of the script

$Step1 =
{
param ([string]$server)

$Global:EventLog = New-Object -type System.Diagnostics.Eventlog -argumentlist Application
$Global:EventLog.Source = “Citrix Reboot Script”

function DisableLogons
{
# Prohibits logons until next restart for server passed as variable 0
set-XAServerLogOnMode -ServerName $args[0] -LogOnMode ProhibitNewLogOnsUntilRestart
Write-Host “Disabling Logons on $server” -foregroundcolor Blue
}

DisableLogons $server
$EventLog.WriteEntry(“Disabled logons until next reboot on ” + $server + “.”,”Information”,”411″)

$sessions = Get-XASession | ? {($_.servername -eq $server -and $_.state -eq “Active”)}
foreach ($session in $sessions)
{
$username = $session.Accountname
Write-Host “Sending message to user $username on $server” -foregroundcolor Blue
Send-XASessionMessage -servername $server -MessageTitle “Server maintenance” -Messagebody “Server will be rebooted in 15 minutes” -sessionID $session.sessionid -MessageboxIcon “error”
}
$EventLog.WriteEntry(“Fifteen (15) minutes warning on server ” + $server + “.”,”Information”,”311″)

}

$Step2 =
{
param ([string]$server)

$Global:EventLog = New-Object -type System.Diagnostics.Eventlog -argumentlist Application
$Global:EventLog.Source = “Citrix Reboot Script”

$sessions = Get-XASession | ? {($_.servername -eq $server -and $_.state -eq “Active”)}
foreach ($session in $sessions)
{
$username = $session.AccountName
Write-Host “Sending message to user $username on $server” -foregroundcolor Green
Send-XASessionMessage -servername $server -MessageTitle “Server maintenance” -Messagebody “Server will be rebooted in 10 minutes” -sessionID $session.sessionid -MessageboxIcon “error”
}
$EventLog.WriteEntry(“Ten (10) minutes warning on server ” + $server + “.”,”Information”,”311″)

}

$Step3 =
{
param ([string]$server)

$Global:EventLog = New-Object -type System.Diagnostics.Eventlog -argumentlist Application
$Global:EventLog.Source = “Citrix Reboot Script”

$sessions = Get-XASession | ? {($_.servername -eq $server -and $_.state -eq “Active”)}
foreach ($session in $sessions)
{
$username = $session.AccountName
Write-Host “Sending message to user $username on $server” -foregroundcolor Yellow
Send-XASessionMessage -servername $server -MessageTitle “Server maintenance” -Messagebody “Server will be rebooted in 5 minutes. SAVE your work” -sessionID $session.sessionid -MessageboxIcon “error”

}
$EventLog.WriteEntry(“Five (5) minutes warning on server ” + $server + “.”,”Information”,”311″)

}

$RebootServer =
{
param ([string]$server)

$Global:EventLog = New-Object -type System.Diagnostics.Eventlog -argumentlist Application
$Global:EventLog.Source = “Citrix Reboot Script”

function StartReboot
{
# Creates a variable named server from the first passed variable
$server = “$args”
# Initiates shutdown on remote server
Invoke-Expression “Shutdown.exe /m $server /r /t 0 /c “”Shutdown scheduled by Citrix Reboot Script.”””
$EventLog.WriteEntry(“Initiating reboot process on ” + $server + “.”,”Information”,”911″)
Start-Sleep -s 120
}

StartReboot $server
}
# Main Script
$workergroup = $GLOBAL:WORKERGROUP
$workergroupservers = @(get-xaworkergroupserver -workergroupname $workergroup | sort-object -property ServerName)
foreach ($workergroupserver in $workergroupservers)
{
$server = $workergroupserver.ServerName
Write-Host “Step1 on $server” -foregroundcolor Blue

$EventLog.WriteEntry(“Processing server ‘” + $server + “‘ from worker group ‘” + $workergroup + “‘.”,”Information”,”211″)
Invoke-Command -ScriptBlock $Step1 -ArgumentList $server
}

Start-Sleep -s 300

foreach ($workergroupserver in $workergroupservers)
{
$server = $workergroupserver.ServerName
Write-Host “Step2 on $server” -foregroundcolor Green

Invoke-Command -ScriptBlock $Step2 -ArgumentList $server
}

Start-Sleep -s 300

foreach ($workergroupserver in $workergroupservers)
{
$server = $workergroupserver.ServerName
Write-Host “Step3 on $server” -foregroundcolor Yellow

Invoke-Command -ScriptBlock $Step3 -ArgumentList $server
}

Start-Sleep -s 300

foreach ($workergroupserver in $workergroupservers)
{
$server = $workergroupserver.ServerName
Write-Host “Rebooting $server” -foregroundcolor Red

Invoke-Command -ScriptBlock $RebootServer -ArgumentList $server
}

#### END

Yes, I do know this could be better and smaller, not to mention improved. The bottom line is, it is a simple script that does the job very well and at the same time it is simple and easy to follow, even for people not used to PowerShell.

I am sure it will help some of you out there. Any comments and suggestions (and even criticism) feel free to reach out. I am all ears.

CR

6,497 total views, no views today

BriForum Boston 2014

This week Brian and Gabe announced the sessions for both BriForums (London/US). I am happy to announce I will be presenting two sessions in Boston and will almost certainly attend BriForum London as a regular peasant.

If you did not read the list of sessions, here is what I am presenting and why I think these will be useful and what the plan is regarding delivering them.

SBC Round Up 2014. I really like doing these. Plan is to go through the installation and testing of several RDS add-ons (i.e ProPalms TSE, Dell vWorkspace, 2X, etc) and see how the compare to each other and of course to RDS 2012 R2 by itself. What will change this year is I am actually recording all the installations and will post all videos as soon as BriForum Boston is over. Also creating individual PDFs for each product installation so at the end you will get an end-to-end guide on how to install every single major product out there. Neat.

RDS-O-Matic. This is basic the end-result of dealing with RDS installs almost on a daily basis for customers around the globe. The idea was to come up with an automated way to create all the PowerShell commands to deploy a full RDS 2012 R2 from scratch. For BriForum it will be able to perform the following tasks:

– Hyper-V only. Creates all the required VMs based on a sysprep’ed VHD. Of course this requires minimum services to be up and running already like your AD, your Hyper-V hosts, the clustering, etc. But if these are there you simply select the VHD you want and it will copy to all the required VMs, mount them, inject the Unattended.xml file and finalize the setup (add to domain, set IP, add to proper OU). This is optional (meaning if you do have all VMs ready to roll you can opt this step out). Yes before you bitch I have no love for VMware ESXi anymore.
– NLB. For every component that needs NLB you will be able to choose if you want it done for you (i.e. RD Gateway). It will create the VIP, add the ports, etc.
– UPD. If you want to enable the User Profile Disk on the deployment.
– Whole deployment. Of course it does that. Sets up the connection brokers, web access, gateway, session hosts, etc. The whole deal.
– SQL Bullshit. Ideally I will try to automate the turd Microsoft created when setting up the SQL for the Connection Brokers HA. It is a PITA (create folder on the SQL, create database, add proper security, etc – amazing how every other product on the market can do this but NOT Microsoft).

The main plan is to turn all this into a web service that anyone can hit, enter the information and get a text file ready to use for the whole deployment. Later iOS and Android apps so you can do that anywhere/anytime/offline.

And for the first time in 10 years of BriForum for me, I will be actually driving to Boston this time what may actually be faster than flying, assuming the cops do not stop me in Maine. Feel free to stop me and say ‘Hi’ if you see me around at BriForum. I will be driving ‘Ferrucio’ (yes, my kids name all the cars we have at home).

Lamborghini Gallardo

Cheers.

CR

5,630 total views, no views today

StoreFront 1.2 Install and how to avoid SQL issues.

As I promised a couple days ago on Twitter, here you have the scripts I used to install StoreFront. It is pretty annoying to realize if you install it using the installer provided by Citrix and let it deal with the database you will get screwed at one point. You will see stupid errors on the StoreFront Web Site, errors on the event log and so on. Resuming: A PITA.

Now if you use the scripts, well then everything gets fixed magically. That leads us to the question why the installer does not clearly state you MUST use the damn scripts to create the database? Or why the installer cannot use the scripts by itself? As I always joke we put people on the moon but we still fail to have installers that can actually install things properly. Amazing.

The first script deals with installing the pre-requisites. I usually use one like this:
powershell -nologo -executionpolicy bypass “& “c:\Installs\SF-PreReq.ps1”

So basically I have a folder on the C: drive called Installs and on it a script called SF-PreReq.ps1 (PowerShell), setting the required execution policy (basically, leave me alone). The contents of SF-PreReq.ps1 are:

Import-Module ServerManager
Add-WindowsFeature as-net-framework
Add-WindowsFeature Web-Server
Add-WindowsFeature Web-Asp-Net
Add-WindowsFeature Web-Windows-Auth
Add-WindowsFeature Web-Metabase
Add-WindowsFeature Web-Http-Redirect
Add-WindowsFeature Web-App-Dev
Add-WindowsFeature Web-Basic-Auth
Add-WindowsFeature Web-Digest-Auth
Add-WindowsFeature Web-Client-Auth
Add-WindowsFeature Web-Cert-Auth
Add-WindowsFeature Web-Url-Auth
Add-WindowsFeature Web-IP-Security
Add-WindowsFeature Web-Dyn-Compression
Add-WindowsFeature Web-Scripting-Tools
Add-WindowsFeature Web-Mgmt-Service
Add-WindowsFeature Web-Mgmt-Compat

I can tell you it works as I did install my production StoreFront 1.2 using it.
Once the pre-requisites are done, then you connect to your SQL box (RDP for example) and using the SQL Management Studio with the proper credentials you will run four scripts in sequence. Make sure to adjust the database name, paths, etc to match whatever you have/decide to use.

First Script – Creates the database

USE [master]

CREATE DATABASE [CitrixStoreFront] ON PRIMARY
( NAME = N’MyApps’, FILENAME = N’C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\DATA\CitrixStoreFront.mdf’ , SIZE = 4096KB ,
MAXSIZE = UNLIMITED, FILEGROWTH = 10% )
LOG ON
( NAME = N’MyApps_log’, FILENAME = N’C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\DATA\CitrixStoreFront_log.ldf’ , SIZE = 560KB ,
MAXSIZE = 2048GB , FILEGROWTH = 10% )
COLLATE latin1_general_CI_AS_KS

IF (1 = FULLTEXTSERVICEPROPERTY(‘IsFullTextInstalled’))
begin
EXEC [CitrixStoreFront].[dbo].[sp_fulltext_database] @action = ‘enable’
end

ALTER DATABASE [CitrixStoreFront] SET ANSI_NULL_DEFAULT OFF
ALTER DATABASE [CitrixStoreFront] SET ANSI_NULLS OFF
ALTER DATABASE [CitrixStoreFront] SET ANSI_PADDING OFF
ALTER DATABASE [CitrixStoreFront] SET ANSI_WARNINGS OFF
ALTER DATABASE [CitrixStoreFront] SET ARITHABORT OFF
ALTER DATABASE [CitrixStoreFront] SET AUTO_CLOSE OFF
ALTER DATABASE [CitrixStoreFront] SET AUTO_CREATE_STATISTICS ON
ALTER DATABASE [CitrixStoreFront] SET AUTO_SHRINK OFF
ALTER DATABASE [CitrixStoreFront] SET AUTO_UPDATE_STATISTICS ON
ALTER DATABASE [CitrixStoreFront] SET CURSOR_CLOSE_ON_COMMIT OFF
ALTER DATABASE [CitrixStoreFront] SET CURSOR_DEFAULT GLOBAL
ALTER DATABASE [CitrixStoreFront] SET CONCAT_NULL_YIELDS_NULL OFF
ALTER DATABASE [CitrixStoreFront] SET NUMERIC_ROUNDABORT OFF
ALTER DATABASE [CitrixStoreFront] SET QUOTED_IDENTIFIER OFF
ALTER DATABASE [CitrixStoreFront] SET RECURSIVE_TRIGGERS OFF
ALTER DATABASE [CitrixStoreFront] SET DISABLE_BROKER
ALTER DATABASE [CitrixStoreFront] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
ALTER DATABASE [CitrixStoreFront] SET DATE_CORRELATION_OPTIMIZATION OFF
ALTER DATABASE [CitrixStoreFront] SET TRUSTWORTHY OFF
ALTER DATABASE [CitrixStoreFront] SET ALLOW_SNAPSHOT_ISOLATION OFF
ALTER DATABASE [CitrixStoreFront] SET PARAMETERIZATION SIMPLE
ALTER DATABASE [CitrixStoreFront] SET READ_COMMITTED_SNAPSHOT OFF
ALTER DATABASE [CitrixStoreFront] SET HONOR_BROKER_PRIORITY OFF
ALTER DATABASE [CitrixStoreFront] SET READ_WRITE
ALTER DATABASE [CitrixStoreFront] SET RECOVERY FULL
ALTER DATABASE [CitrixStoreFront] SET MULTI_USER
ALTER DATABASE [CitrixStoreFront] SET PAGE_VERIFY NONE
ALTER DATABASE [CitrixStoreFront] SET DB_CHAINING OFF

Second Script – Creates the tables

USE [CitrixStoreFront]

/****** Object: Table [dbo].[User] ******/
SET ANSI_NULLS ON

SET QUOTED_IDENTIFIER ON

CREATE TABLE [dbo].[User](
[id] [int] IDENTITY(1,1) NOT NULL,
[username] [nvarchar](100) COLLATE latin1_general_CS_AS_KS NOT NULL,
CONSTRAINT [PK_users] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]
) ON [PRIMARY]

CREATE UNIQUE NONCLUSTERED INDEX [username_idx] ON [dbo].[User]
(
[username] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,
ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]

/****** Object: Table [dbo].[Subscription] ******/
SET ANSI_NULLS ON

SET QUOTED_IDENTIFIER ON

CREATE TABLE [dbo].[Subscription](
[id] [int] IDENTITY(1,1) NOT NULL,
[subscription_ref] [varchar](32) COLLATE latin1_general_CS_AS_KS NOT NULL,
[resource_id] [nvarchar](400) COLLATE latin1_general_CS_AS_KS NOT NULL,
[user_id] [int] NOT NULL,
[status] [int] NOT NULL,
[metadata] [nvarchar](max) NULL,
[secure_metadata] [nvarchar](max) NULL,
CONSTRAINT [PK_subscriptions] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]
) ON [PRIMARY]

CREATE UNIQUE NONCLUSTERED INDEX [subscription_ref_idx] ON
[dbo].[Subscription]
(
[subscription_ref] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,
ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]

CREATE NONCLUSTERED INDEX [user_resource_idx] ON [dbo].[Subscription]
(
[user_id] ASC,
[resource_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,
ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]

/****** Object: Default [DF_subscriptions_status] ******/
ALTER TABLE [dbo].[Subscription]
ADD CONSTRAINT [DF_subscriptions_status]
DEFAULT ((0)) FOR [status]

/****** Object: ForeignKey [FK_subscriptions_user_id] ******/
ALTER TABLE [dbo].[Subscription]
WITH CHECK ADD CONSTRAINT [FK_subscriptions_user_id]
FOREIGN KEY([user_id])
REFERENCES [dbo].[User] ([id])

ALTER TABLE [dbo].[Subscription]
CHECK CONSTRAINT [FK_subscriptions_user_id]

CREATE TABLE [dbo].[SchemaDetails](
[major_version] [int] NOT NULL,
[minor_version] [int] NOT NULL,
[details] [nvarchar](max) NULL
) ON [PRIMARY]

INSERT INTO [dbo].[SchemaDetails] ([major_version], [minor_version])
VALUES (1, 0)

Third Script – Assigns the correct login to the database. This one you need to create on the SQL server a local group with whatever name and add the StoreFront servers to it. In my case I used StoreFrontServers as the local group on the SQL Server.

A local group on the SQL Server must be created.
A local group on the SQL Server must be created.

USE [master]
CREATE LOGIN [YOUR_SQL_SERVER\StoreFrontServers] FROM WINDOWS;
ALTER LOGIN [YOUR_SQL_SERVER\StoreFrontServers]
WITH DEFAULT_DATABASE = [CitrixStoreFront];

Fourth Script – Fixes permissions on the database

USE [CitrixStoreFront]
CREATE USER [CitrixSubscriptionDBUsers] FOR LOGIN [YOUR_SQL_SERVER\StoreFrontServers];

EXEC sp_addrolemember N’db_datawriter’, N’CitrixSubscriptionDBUsers’;
EXEC sp_addrolemember N’db_datareader’, N’CitrixSubscriptionDBUsers’;

That is it. Once you have all the scripts done (in sequence) on the SQL Server you can then fire up the StoreFront installation. All the pre-requisites will be already in place and the database created. You simply follow the wizard and you are all set.
Make sure you do have a certificate installed on the StoreFront server before you fire up the install (do the whole certificate thing right AFTER you run the pre-requisites script and make sure HTTPS is bound to IIS) .

That is all. StoreFront should now install properly.

CR

10,471 total views, 4 views today