Reporting/Monitoring, SQL

Director Under the Hood: New Users

Intro

Director is Citrix’s new metrics and monitoring dashboard. The interface is modern and the emphasis is on real-time information about your users. It consolidates information about your environment and makes it easy to differentiate between applications and desktops. If your only experience has been with EdgeSight in the past then you’ll see Director as a breath of fresh air.

There’s a lot of good views and data in the new Citrix Director and the “one pane of glass” view of your environment is pursued by all 3rd party monitoring, reporting, and alerting vendors. Unfortunately, it’s not easy to get all the same data I’ve gathered in past from the Director database. In this post we’ll look at tracking new users connecting to your Citrix environment.

For information on the database schema…read my previous article on Director.

New Users

I collect lots of metrics to report on my environment. One of the ones I track is the number of new users that connect to my Citrix environment. I view this metric as speaking to the overall adoption rate of my Citrix platform as well as a leading indicator for growth. Can we find this info in the Director Trends dashboard?

The short answer is no. The long answer is noooooooooooooooooooooooooooooo. In fact, it is not possbile to track this in EdgeSight. In a previous job, we worked around this by adding a USER table to the Edgesight database and then ran a query to compare the unique users who logged in that past month against the USER table. Who ever did not show up in the USER table was considered new.

SELECT distinct [user]
FROM vw_ctrx_archive_server_start_perf AS ESdata
WHERE [user]  'UNKNOWN'
and convert(varchar(10),time_stamp,111) between '2016/05/01'
and '2016/05/31'
and (NOT EXISTS
(SELECT distinct userid
FROM userarchive
WHERE (userarchive.userid = ESData.[user]))) order by [user]

The above query gets all the unqiue users who logged in between May 1st and May 31st (using the Edgesight view: vw_ctrx_archive_server_start_perf). It then compares this list against the userarchive table that we created to store the username and some other data about our users. Thus we got  a count of new users to our Citrix environment. Once we completed our monthly reporting, we added these new users to the userarchive table.

You say, “That’s great Alain. Wow! How the heck do I do this in Director?”

I say…

“SQL To the Rescue!”

For this query I’m using only one table:

MonitorData.User (Table)
image

I select the month and year and then count the usernames for that month and year. The great thing about this table is that it only creates a new row the first time a user connects to the system automatically. So, the following query will give you a easy way to see the new users who connected to your Citrix envrionment.

SELECT convert(char(9),datename(month,CreatedDate)) + ' '
+ convert(char(4),datepart(year,CreatedDate)) as 'Month',
count (Username) as 'New Users'
FROM MonitorData.[User]
GROUP BY convert(char(9),datename(month,CreatedDate)) + ' '
+ convert(char(4),datepart(year,CreatedDate))

MonitorData.User_query

In conclusion

I hope this encourages you to take a look under the hood of Director to see what you can get out of it. The database infrastructure is much, much simpler than EdgeSight and should provide a lot of good detail.

Thanks,
Alain

Reporting/Monitoring, SQL

Director Under the Hood: Total Sessions and Unique Users Per Day

Intro

Director is Citrix’s new metrics and monitoring dashboard. The interface is modern and the emphasis is on real-time information about your users. It consolidates information about your environment and makes it easy to differentiate between applications and desktops. If your only experience has been with EdgeSight in the past then you’ll see Director as a breath of fresh air.

There’s a lot of good views and data in the new Citrix Director and the “one pane of glass” view of your environment is pursued by all 3rd party monitoring, reporting, and alerting vendors. Unfortunately, it’s not easy to get all the same data I’ve gathered in past from the Director database. In this post we’ll look at a query to show you the total sessions and unique users per day.

This is it…really?

The tables that make up the Director Database

The views that make up the Director Database

image image

After years of pouring through and querying EdgeSight’s tables and views, I first thought that something must be wrong. This can’t be all there is to the Director database, but that’s all there is.  Before we dive into SQL, let’s see what we can find using the Director GUI. I like to collect lots of metrics when I report on my environment. The 3 main session metrics I track are concurrent user per day, unique users per day and total sessions per day. Can we find this info in the Director Trends dashboard?

I set the Time period to Last Month and then set to custom ending to 10/1/2015. This should give me data for September 2015. Here’s what we get:

image

NOTE: For these examples, I’m looking at all delivery groups. You can limit your view by delivery group if you wanted to track metrics for different groups of users.

As you can see, we get a pretty graph, but we have to export the data to Excel to get precise detail:

image

What this doesn’t show us it how many sessions and unique users there are per day. The only way to get this using the Director interface is to click on a point on the graph to see the session details. This will only work for more recent time period.

image

SQL To the Rescue

For this query I’m using the following tables/views:

MonitorData.SessionV1 (View) MonitorData.Connection (Table) MonitorData.User (Table)
image image image

I’m linking the SessionV1 and Connection SessionKey columns together and the User.id and SessionV1.userid columns together. This ensures that I’m grouping the same sessions and users together (users can have more than one session). Then I group by the LogOnStartDate and count the distinct sessionkeys and distinct userids. This gives me the total sessions and unique users per day.
This query will pull all available data and total the sessions and unique users per day.

select convert(varchar(10),LogOnStartDate,111) as 'Date', count (distinct MonitorData.SessionV1.sessionKey) as 'Total Sessions', count (distinct MonitorData.SessionV1.Userid) as 'Unique Users'
from MonitorData.SessionV1,MonitorData.Connection,MonitorData.[User]
where FailureDate is NULL and MonitorData.SessionV1.SessionKey = MonitorData.Connection.SessionKey
and MonitorData.[User].Id = MonitorData.SessionV1.userid
group by convert(varchar(10),LogOnStartDate,111)
order by convert(varchar(10),LogOnStartDate,111)

image
The following query is similar, but it just pulls data for the current month.

DECLARE @mydate DATETIME
Set @mydate = GETDATE()
select convert(varchar(10),LogOnStartDate,111) as 'Date', count (distinct MonitorData.SessionV1.sessionKey) as 'Total Sessions', count (distinct MonitorData.SessionV1.Userid) as 'Unique Users'
from MonitorData.SessionV1,MonitorData.Connection,MonitorData.[User]
where FailureDate is NULL and MonitorData.SessionV1.SessionKey = MonitorData.Connection.SessionKey
and MonitorData.[User].Id = MonitorData.SessionV1.userid
and convert(varchar(10),LogOnStartDate,111) between CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(@mydate)-1),@mydate),111)
and CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,@mydate))),DATEADD(mm,1,@mydate)),111)
group by convert(varchar(10),LogOnStartDate,111)
order by convert(varchar(10),LogOnStartDate,111)

image

This query groups by the current month, so you can get the total unique sessions and users for the current month:

DECLARE @mydate DATETIME
Set @mydate = GETDATE()
select convert(char(9),datename(month,LogOnStartDate)) + ' ' + convert(char(4),datepart(year,LogonStartDate)) as 'Month',
count (distinct MonitorData.SessionV1.sessionKey) as 'Total Sessions',
count (distinct MonitorData.SessionV1.Userid) as 'Unique Users'
from MonitorData.SessionV1,MonitorData.Connection,MonitorData.[User]
where FailureDate is NULL
and MonitorData.SessionV1.SessionKey = MonitorData.Connection.SessionKey
and MonitorData.[User].Id = MonitorData.SessionV1.userid
and convert(varchar(25),LogOnStartDate,107) between CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(@mydate)-1),@mydate),107)
and CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(DATEADD(mm,1,@mydate))),DATEADD(mm,1,@mydate)),107)
group by convert(char(9),datename(month,LogOnStartDate)) + ' ' + convert(char(4),datepart(year,LogonStartDate))

image

This query is similar to above, but takes all the available data and groups it by month:

select convert(char(9),datename(month,LogOnStartDate)) + ' ' + convert(char(4),datepart(year,LogonStartDate)) as 'Month',
count (distinct MonitorData.SessionV1.sessionKey) as 'Total Sessions',
count (distinct MonitorData.SessionV1.Userid) as 'Unique Users'
from MonitorData.SessionV1,MonitorData.Connection,MonitorData.[User]
where FailureDate is NULL
and MonitorData.SessionV1.SessionKey = MonitorData.Connection.SessionKey
and MonitorData.[User].Id = MonitorData.SessionV1.userid
group by convert(char(9),datename(month,LogOnStartDate)) + ' ' + convert(char(4),datepart(year,LogonStartDate))

image

In conclusion

I hope this encourages you to take a look under the hood of Director to see what you can get out of it. The database infrastructure is much, much simpler than EdgeSight and should provide a lot of good detail.

Thanks,
Alain

Administration, Application

Application woes: Solarwinds Advanced Subnet Calculator

bg_adv_subnet_calc_top

Solarwinds Advanced Subnet Calculator (ASC) is a great, freely-available tool to generates lists of IP4 networks. I recently ran into an issue installing it in a Windows 7 virtual desktop. It would run fine for administrators, but regular users received errors like this and this.

I broke out Process Monitor and Regshot to investigate what was going on here. I found that regular users could not register the SWLogo.ocx file listed in the above link. This makes sense as I had applied reasonable security on non-admins.
I reinstalled the ASC as the local admin (i.e. non-domain administrator) and that fixed one error for users, but a new one popped up. Running Process Monitor again, I found that ASC was expecting read/write permissions on

c:\programdata\Solarwinds\VB\Banners.

asc_error

This folder contains a series of bitmap banner ads for Solarwinds products. Not at all unreasonable for a free tool. Once I modified permissions in the disk image to allow users to modify (ha!) this folder I was back in business.

Thanks,
Alain

Administration, Application, Scripting

Powershell: XenDesktop 7.x Published Applications

xendesktop7iscoming

Intro

Citrix XenDesktop 7 represents a major change from previous versions of Citrix. Those of us who have managed large XenApp-based farms with many published applications will find us rushing to scour the Internet for how-to’s and Citrix documentation because everything “seems” very different.

You are not wrong…things are different, and if you are considering a move to XenDesktop 7.x, you owe it to yourself to document (in detail) what you have done in your current XenApp 6.5, 5.0, etc environment and examine how to do the same thing XenDesktop 7.x. In this blog post, we will look at a PowerShell script that will automate the creation of published applications in XenDesktop 7.6.

Scenario

I’m migrating a XenApp 6.5 / XenDesktop 5.6 environment to XenDesktop 7.6. In the old XenApp farm, users run a split Access database as a published application with many front-ends pointing to a single back-end. The idea of recreating 60+ published applications (each assigned to a single user) with the Citrix Studio GUI is like a dream come true…no I mean a nightmare.

I wanted to approach this with a script to automate the front-end creation and to minimize any user error. This could allow a lower-level admin or helpdesk member to create the front-end without opening up the Citrix Studio.

The Script

NOTE: The new-brokerapplication cmdlet resides in the Citrix.Broker.Admin.V2 module. It should available on any system/workstation with the Citrix Studio installed.

.SYNOPSIS
    Takes a username or list of usernames and creates the same published application for each user.
.DESCRIPTION
    Takes a username or list of usernames and creates the same published application for each user.

    It is recommended that this script be run as an AD and Citrix admin. In addition, the Citrix and MS Active Directory Powershell module should be available for user lookup and published application creation..
.PARAMETER username
    Required parameter.
    User account(s) that will be assigned published application. 
.PARAMETER pubappname
    Required parameter.
    Common name for published application. Script will append the username to this parameter so it will be unqiue for each user. 
.PARAMETER applocation
    Required parameter.
    Location of executable. Used for location in published application.
.PARAMETER cmmdline
    Optional parameter.
    Location of file or optional parameter that could go into command line field for a published application.
.PARAMETER DeliveryGroup
    Required parameter.
    Which Delivery Group to publish application to.
.PARAMETER DeliveryController
    Required parameter.
    Which Citrix Delivery Controller (farm) to publish applicaiton with
.EXAMPLE
    PS C:\PSScript > .\create-pubapp.ps1 -username TESTUSER -applocation "C:\app.exe" -DeliveryGroup "Production" -DeliveryController "CitrixStudio"
    
    Will create a new published application (c:\app.exe) assigned to TESTUSER and published to the Production Delivery Group.
.EXAMPLE
    PS C:\PSScript > .\create-pubapp.ps1 -username TESTUSER -applocation "C:\app.exe" -DeliveryGroup "Production" -DeliveryController "CitrixStudio" -verbose
    
    Will create a new published application (c:\app.exe) assigned to TESTUSER and published to the Production Delivery Group.
    Feedback and progress messages will be displayed.
.INPUTS
    None.  You cannot pipe objects to this script.
.OUTPUTS
    No objects are output from this script.  
    This script creates a Xendesktop 7.x published application.
.NOTES
    NAME: create-pubapp.ps1
    VERSION: 2.00
    CHANGE LOG - Version - When - What - Who
                 1.00 - 07/1/2015 - Initial script - Alain Assaf
                 1.01 - 07/6/2015 - Modified module import fuctions to use -like instead of -eq
                                    Created new parameter to capture delivery controller
                 2.00 - 07/6/2015 - Major edit to make this script more generic
    AUTHOR: Alain Assaf
    LASTEDIT: July 6, 2015
.LINK
    http://www.linkedin.com/in/alainassaf/
    http://wagthereal.com
    http://www.icenlemon.co.uk/blog/?p=307
    http://stackoverflow.com/questions/8388650/powershell-how-can-i-stop-errors-from-being-displayed-in-a-script

Param(
    [parameter(Position = 0, Mandatory=$True )]     
    [ValidateNotNullOrEmpty()]
    $username,
    
    [parameter(Position = 1, Mandatory=$True )]     
    [ValidateNotNullOrEmpty()]
    $pubappname,
    
    [parameter(Position = 2, Mandatory=$True )]     
    [ValidateNotNullOrEmpty()]
    $applocation,

    [parameter(Position = 3, Mandatory=$False )]     
    [ValidateNotNullOrEmpty()]
    $cmmdline,
    
    [parameter(Position = 4, Mandatory=$True )]     
    [ValidateNotNullOrEmpty()]
    $DeliveryGroup,
    
    [parameter(Position = 5, Mandatory=$True )]     
    [ValidateNotNullOrEmpty()]
    $DeliveryController
    )

#Constants
$datetime = get-date -format "MM-dd-yyyy_HH-mm"
$ScriptRunner = (get-aduser $env:username | select name).name
$PSModules = ("activedirectory")
$PSSnapins = ("*citrix*")
$compname =  (gci env:COMPUTERNAME).value
#$ErrorActionPreference= 'silentlycontinue' # Comment out to see all error messages
$addomain = (Get-ADDomain).name

### FUNCTION: get-mymodule #####################################################
Function Get-MyModule {
    Param([string]$name)
    if(-not(Get-Module -name $name)) {
        if(Get-Module -ListAvailable | Where-Object { $_.name -like $name }) {
            Import-Module -Name $name
            $true
        } #end if module available then import
        else { $false } #module not available
        } # end if not module
    else { $true } #module already loaded
}  
### FUNCTION: get-mymodule #####################################################

### FUNCTION: get-mysnapin #####################################################
Function Get-MySnapin {
    Param([string]$name)
    if(-not(Get-PSSnapin -name $name)) {
        if(Get-PSSnapin -Registered | Where-Object { $_.name -like $name }) {
            add-PSSnapin -Name $name
            $true
        } #end if module available then import
        else { $false } #snapin not available
        } # end if not snapin
    else { $true } #snapin already loaded
}  
### FUNCTION: get-mysnapin #####################################################

### FUNCTION: Check-ADUser #####################################################
#Function to check if user account exists in AD
Function Check-ADUser
{
    Param ($usrname)
 
    $isuser = $(try {Get-ADUser $usrname} catch {$null})
    if ($isuser -ne $null) {
        return $true
    } else {
        return $false
    }  
}
### FUNCTION: Check-ADUser #####################################################

#Import Module(s) and Snapin(s)
foreach ($module in $PSModules) {
    if (!(get-mymodule $module)) {
        write-verbose "$module PowerShell Cmdlet not available."
        write-verbose "Please run this script from a system with the $module PowerShell Cmdlets installed."
        exit
    }
}
foreach ($snapin in $PSSnapins) {
    if (!(get-MySnapin $snapin)) {
        write-verbose "$snapin PowerShell Cmdlet not available."
        write-verbose "Please run this script from a system with the $snapin PowerShell Cmdlets installed."
        exit
    }
}

#Confirm Delivery Group Exists
$testDG = Get-BrokerDesktopGroup $DeliveryGroup -AdminAddress $DeliveryController
if ($testDG -eq $null) {
    write-verbose "Delivery group - $DeliveryGroup - DOES NOT EXIST. Exiting"
    exit
}

#Confirm applicaiton exists on machine you are running the script from. If False warn admin.
if (test-path $applocation) {
} else {
    write-verbose "Published Application Location - $applocation - is not valid on $compname"
}

foreach ($user in $username) {
    if (Check-ADUser $user) { #User Exists - proceed
        write-verbose "User -$user - exists"
        $pubname = $pubappname + "-" + $user
        $newapp = new-BrokerApplication -name $pubname -commandlineexecutable $applocation -DesktopGroup $DeliveryGroup -ApplicationType HostedOnDesktop -CommandLineArguments $cmmdline -UserFilterEnabled $true -AdminAddress $DeliveryController 
        Add-BrokerUser "$addomain\$user" -Application $newapp.Name
    } else {
        write-verbose "User - $user - DOES NOT EXIST"
    }
}

Wait…what about the published application icon?

One of the parameters in the new-brokerapplication cmdlet is IconUid. If you want to assign something other than the default you can add this parameter to line 168 in the above script. I did not find an automated way to assign this icon. So, in this case I created an application using the GUI (and chose a non-default icon) and then ran this powershell command to see what the iconuid is:

get-brokerapplication -name "Notepad"
AdminFolderName                  :
AdminFolderUid                   : 3
ApplicationName                  : Notepad
ApplicationType                  : HostedOnDesktop
AssociatedDesktopGroupPriorities : {0}
AssociatedDesktopGroupUUIDs      : {1a9c3138-d236-4d70-a20b-78ff85d55897}
AssociatedDesktopGroupUids       : {4}
AssociatedUserFullNames          : {Domain Users}
AssociatedUserNames              : {Domain\Domain Users}
AssociatedUserUPNs               : {}
BrowserName                      : Notepad
ClientFolder                     : 
CommandLineArguments             : 
CommandLineExecutable            : C:\windows\system32\notepad.exe
CpuPriorityLevel                 : Normal
Description                      : 
Enabled                          : True
IconFromClient                   : False
IconUid                          : 2
MetadataKeys                     : {}
MetadataMap                      : {}
Name                             : Notepad
PublishedName                    : Notepad
SecureCmdLineArgumentsEnabled    : True
ShortcutAddedToDesktop           : False
ShortcutAddedToStartMenu         : False
StartMenuFolder                  : 
UUID                             : c7eb9ced-c7b4-4ea7-b58a-9157e06c144f
Uid                              : 14
UserFilterEnabled                : True
Visible                          : True
WaitForPrinterCreation           : False
WorkingDirectory                 :

Now you have the iconuid and it will apply the same icon to each published application instance.

Take care and have fun with XenDesktop 7.x

Thanks,
Alain

Administration

Storefront: Resolving Duplicate Icons

ronburgundy
NOTE: This post concerns Storefront 2.6

Intro

I’ve spent quite a bit of time branding my new Storefront site for my users. One of the persistent issues I ran into was that all my desktop and application icons were duplicated. Here is how it was resolved.

The problem

I had configured my Storefront servers in an HA configuration by load balancing them via NetScaler and I suspected that this was somehow duplicated the icons. After some research I found that this was not the case. Citrix Studio did not show any duplicate applications, and I was not configured for Storefront Multi-Site settings (this can result in duplicate applications as well see this Citrix article for details on configuring this). Despite this I saw the following:

dupicons1
Desktop icons

dupicons2
Apps list

The solution

Turns out that when I configured my Storefront site, I needlessly entered 2 separate entries into the Manage Delivery Controllers section.

mandc1

You are able to enter in any number of delivery controllers in one entry and Storefront will load balance between the two (or more). Ideally, if you have a NetScaler (or similar device), you can load balance your Delivery Controllers there. I Removed the Controller2 entry above and edited the Controller1 entry as below.

mandc3

Now there are 2 servers listed for one entry and Storefront will load balance between them.

mandc2

Done with changes. Let’s log into Storefront and see what’s different:

dupicons3 dupicons4

Thanks,
Alain

Administration

Storefront Customization: Logoff behavior

cap_obvious
Captain Obvious strikes again!

Note: This post concerns Storefront 2.6

Intro

While testing 2 users logging into a desktop and having a different experience (regular user vs. admin), I was perplexed on the behavior of Storefront.

This isn’t your father’s Web Interface

I logged in as one user and launched a desktop. When I signed out of Storefront, in order to sign in as a different user, my virtual desktop immediately disconnected. I had run across the behavior before when using Web Interface, but was hard pressed to find an equivalent setting in the Storefront GUI to fix it.

After digging through Citrix edocs I found: To configure workspace control.

  1. Open web.config under C:\inetpub\wwwroot\Citrix\storenameWeb\
  2. Find the line containing:

    <workspaceControl enabled=”true” autoReconnectAtLogon=”true” logoffAction=”disconnect” showReconnectButton=”false” showDisconnectButton=”false” />

  3. Change the value of logoffAction to one of the following settings:
    1. disconnect (default) – Disconnects user from applications and desktops
    2. terminate – Shut down user’s applications and desktops
    3. none – leave sessions running and active after Storefront log off.
  4. The article details other settings, namely if you set showReconnectButton and showDisconnectButton to true, you will get those options in a drop down for your users (similar to Web Interface).

Thanks,
Alain Assaf

Administration, Scripting

PowerShell: Display Maintenance Message for a XenApp Published Application

wherearemycitrixapps

Intro

For years, management of Citrix Published Applications has been pretty heavy-handed. You had the following options:

  1. You had enough servers to ensure your users always had access to their apps.
  2. You established a regular maintenance period that everyone knew about (I’m trying not to burst out laughing) that allowed you to perform application and server maintenance.
  3. You implemented an application virtualization technology that allowed for incremental testing and roll-out of application changes without affecting all your users.
  4. You used the Citrix console to disable and hide an application (resulting in calls to the help desk like the above picture).

image

In this blog post I’ll present a PowerShell script that allows you to display a maintenance message instead of hiding the application. This allows you to do maintenance and give users some feedback about what’s going on.

Disabling the application

We’re assuming the runner of the script has XenApp farm admin permissions enough to change published application properties and that the XenApp 6.5 SDK is installed.  When the change-appmaintenance.ps1 script runs, it creates a text file with the same name as the published app under C:\_scripts\appmaintenance that stores 3 items of information (this assumes the –disabled flag is used):

  1. The original Command Line field of the published application (used when we need to put the application back in production.
  2. The person who ran the script.
  3. The current date and time (when the script was run).

Here’s a command line example (the –verbose flag is optional, but shows feedback messages):

PS C:\_scripts> .\change-appmaintenance.ps1 -PublishedApp "testdisable" -disable –Verbose

Here is the maintenance file created for the TestDisable published application.

image

Once the information is saved, the script changes the command line to the following:

image

The show-appmaintenancebox.ps1 script is simple, but you can add more information as needed. You could further customize the maintenance message by passing parameters, but you may run into the character limit (256 characters) in the Command Line field. In this case you will get the following message:

image

You could also solve the above issue by calling a batch file to run the script.

$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("This application is unavailable because it is undergoing maintenance. Please try again later.",0,"APPLICAITON UNDER MAINTENANCE",0)

NOTE: More info about the Popup Method is here.

When a user launches the application, the script now displays this window.

image

Enabling the Application

To reverse the changes you run the same command without the –Disabled flag (the –verbose flag is optional, but shows feedback messages):

PS C:\_scripts> .\change-appmaintenance.ps1 -PublishedApp "testdisable" –Verbose

The script will check for the published application maintenance file under C:\_scripts\appmaintenance, read the file, and use the first line to change the Command Line back to the correct one for the application. Then it will delete the maintenance file.

The Script

<#
.SYNOPSIS
	Disables (or re-enables) a published application by replacing the original command line with one that
 calls a pop-up window.
.DESCRIPTION
	Disables (or re-enables) a published application by replacing the original command line with one that
 calls a pop-up window. 

	It is recommended that this script be run as an a XenApp Farm administrator.
 This script assumes that the XenApp 6.5 powershell commandets are installed.
.PARAMETER PublishedApp
    Required parameter. Application that will be enabled or disabled.
.PARAMETER Disable
    Conditional parameter.
    If present, the application is disabled and maintenance message will be displayed. If not, then the maintenance message
    is removed and applicaiton is republished.
.EXAMPLE
	PS C:\PSScript > .\change-appmaintenance.ps1 -PublishedApp $PublishedApplicationName

	Script will try and re-enable the application.
 Will not display feedback
.EXAMPLE
	PS C:\PSScript > .\change-appmaintenance.ps1 -PublishedApp $PublishedApplicationName -verbose

	Script will try and re-enable the application.
 Will display feedback
.EXAMPLE
	PS C:\PSScript > .\change-appmaintenance.ps1 -PublishedApp $PublishedApplicationName -Disabled -verbose

	Script will disable the application.
 Will display feedback
.INPUTS
	PublishedApp maintenance file. If the file exists it assumes that the application was already put in maintenance. The script will read in the
    information from the file and take it out of maintenance (assuming the disabled flag is not present). It will delete the existing file if
    taking the application is taken out of maintenance.
.OUTPUTS
	A maintenance file will be generated per application (assuming the disabled switch is present). The file will contain the original location of the application from the published application
    properties, the name of the script runner, and the date the change was made.
.NOTES
	NAME: change-appmaintenance.ps1
	VERSION: 1.00
    CHANGE LOG - Version - When - What - Who
                 1.00 - 08/15/2014 - Inititail script - Alain Assaf
	AUTHOR: Alain Assaf
	LASTEDIT: August 15, 2014
.LINK
 http://www.linkedin.com/in/alainassaf/
  http://wagthereal.com
  http://gallery.technet.microsoft.com/scriptcenter/PowerShell-Message-Box-6c6e4f75
#>

Param(
    [parameter(Position = 0, Mandatory=$true )]
    [ValidateNotNullOrEmpty()]
	[string]$PublishedApp,

    [parameter(Position = 1, Mandatory=$False )]
    [ValidateNotNullOrEmpty()]
    [switch]$Disable)

### FUNCTION: get-mysnapin ####################################################
Function Get-MySnapin {
    Param([string]$name)
    if(-not(Get-PSSnapin -name $name)) {
        if(Get-PSSnapin -Registered | Where-Object { $_.name -eq $name }) {
            add-PSSnapin -Name $name
            $true
        } #end if module available then import
        else { $false } #snapin not available
        } # end if not snapin
    else { $true } #snapin already loaded
}
### FUNCTION: get-mysnapin ####################################################

#Constants
$datetime = get-date -format "MM-dd-yyyy_HH-mm"
$Domain=(Get-WmiObject Win32_ComputerSystem).Domain
$ScriptRunner = (get-aduser $env:username | select name).name
$XAZDC = "XenApp65ZDC.local"
$appmaintfolder = "C:\_scripts\appmaintenance"
$PSSnapins = ("Citrix.XenApp.Commands")
$XApps = New-Object System.Collections.ArrayList

#Populate XApps
foreach ($a in $PublishedApp) {$XApps.Add($a)>$null}

#Import Snapin(s)
foreach ($snapin in $PSSnapins) {
    if (!(get-MySnapin $snapin)) {
        write-verbose "$snapin PowerShell Cmdlet not available."
        write-verbose "Please run this script from a system with the $snapin PowerShell Cmdlets installed."
        exit
    }
}

#Confirm application(s) exists
foreach ($apps in $XApps) {
    $appresult = Get-XAApplication -ComputerName $XAZDC -BrowserName $apps -ErrorAction SilentlyContinue
    if ([boolean]($appresult)) {
        write-verbose "$apps exists"
        if ($Disable) {   #Disable flag is present
            $appmaintfile = $apps + ".txt"
            if (test-path -Path $appmaintfolder\$appmaintfile) {
                write-verbose "$appmaintfile already exists. Please confirm application name. No changes made"
            } else {      #Create maintenance file and replace app's command line
                add-content -Value $appresult.CommandLineExecutable -Path $appmaintfolder\$appmaintfile
                Add-Content -Value $ScriptRunner -Path $appmaintfolder\$appmaintfile
                Add-Content -Value $datetime -Path $appmaintfolder\$appmaintfile
                Set-XAApplication -computername $XAZDC -BrowserName $apps -CommandLineExecutable "powershell.exe -noprofile -executionpolicy bypass -command c:\_scripts\Show-appmaintenancebox.ps1"
            }
        } else {          #Read maintenance file and restore app's command line
            $appmaintfile = $apps + ".txt"
            if (test-path -Path $appmaintfolder\$appmaintfile) {
                $fileinfo = get-content -path $appmaintfolder\$appmaintfile
                Set-XAApplication -computername $XAZDC -BrowserName $apps -CommandLineExecutable $fileinfo[0]
                remove-item -Path .\appmaintenance\$appmaintfile
                Write-Verbose "Application maintenance removed for $apps"
            } else {
                write-verbose "*** ERROR *** expected maintenance file:$appmaintfile is missing. Please confirm application name. Script will exit"
                exit
            }
        }
    } else {
        Write-Verbose "$apps does not exist"
    }
}

 

Script Logic Flow

change-appmaintenance_scriptflow

Future tasks

To use this with your users, I would recommend that you try calling the PowerShell script with VBS. This should allow you to hide the PowerShell window that calls the script. You can read more here: Hiding a Powershell window using VBscript

I hope you found this script useful and that it may help you. I welcome all comments and suggestions.

Thanks,
Alain