Nov 242015
 

Have you ever excluded a directory after the fact, and realized that Citrix Profile Manager does not remove the directory from the store? Have you noticed “AppData\Local\Microsoft\Internet Explorer\DOMStore” or “AppData\Roaming\Microsoft\Windows\Recent” filling up with tons of small files/empty folders – most of which are old as dirt (delaying logon)? Have you pulled out your hair over the tons of junk OICE folders showing up in AppData? I have a solution for all of this mess… I wrote another script!

What you need:

Powershell of course – minimum v2 I think
An account which has rights to go through all of the profiles and delete the junk files/folders (run the script as that user if it isn’t you).
A .csv file with your exclusion/sync list (read the CSV portion in the comments of the script – muy importante!)

Here is the script

 

Nov 052015
 

As a Citrix CSP, having a good set of scripts to deploy a base environment is critical. Setting up 50 environments by hand would take far more time than with good scripting. Now that I’ve finally had some time to sit down and not be working on 6.5 stuff, I have been able to write scripts to install and configure XenDesktop 7.6 with Storefront 3 using PowerShell (and some batch). Full disclosure! I am NOT good at PowerShell, or scripting in general. I’m sure there are a million better/easier ways to do these things, but what I have works, dammit. The point of this blog is to show you the commands, and what they do. I will attach my final scripts where you can tune and tweak as needed.

As always, credit where credit is due. I took some code from Aaron Parker from here. I also took some code from Eric from here. Lastly, I’d like to give a BIG THANKS to Esther Barhel (@virtuEs_IT) for helping me out with the non-documented Storefront 3 PowerShell commands.

If you have read my blog, most of my environments are built for the SMB/SMG space. That being said in this blog we will be setting up a single instance configuration. There will be a single server with both the XDC and Storefront roles on it. You could easily take this code to add additional XDCs or Storefront servers to your environment. I will be using code snippets to explain what they are for during the blog.

The first thing we want to do is install XenDesktop. As this is a small environment, we are going to use SQL Express on the XDC. Personally, I do not like to use SQL instances, so in my configuration I am going to install SQL with the default instance instead of the SQLEXPRESS instance. For this, I have created a directory with the XD7.6 disk extracted and shared on the network. I then created a SQL directory and extracted SQL2014 Express in there. I shared this directory out as XA76. (Long live XenApp!)

Installing SQL and XenDesktop

This first “Script” simply does a net use to the share, installs SQL, then installs XenDesktop with only the Controller and Studio roles. Note that you do not want to install Storefront yet. This script will reboot the server. As a scripting/PowerShell noobie, I learned a lot during this. For example. In PowerShell, the use of –% tells PowerShell to stop parsing code after those characters. This helped me a lot because PowerShell was trying to parse the \’s and /’s and failing miserably.

net use --% x: \\10.56.90.20\XA76
x:
cd '.\SQL'
write-host "Installing SQL"
 .\SETUP.EXE --% /QS /ACTION=install /FEATURES=SQL,Tools /INSTANCENAME=MSSQLSERVER  /IAcceptSQLServerLicenseTerms=true
cd ..
cd '.\x64\XenDesktop Setup'
write-host "Installing XenDesktop"
.\XenDesktopServerSetup.exe --% /components CONTROLLER,DESKTOPSTUDIO /NOSQL /quiet /configure_firewall
shutdown -f -r -t 2

XenDesktop Configuration

After the server is done rebooting, we can start with the configuration of the XenDesktop Site. First thing we want to do is setup the databases. I’m going to setup the default databases to the local system we just installed SQL on, using the NetBIOS Name of the domain as the site name. In this example $NBN is NetBIOS Name.

New-XDDatabase -AllDefaultDatabases -DatabaseServer $env:COMPUTERNAME -SiteName $NBN

The next thing we want to do is setup the Site. In my configuration, I use the NetBIOS name and append it to the DBs. In this example $LDB would be CitrixConfigLoggingTEST where TEST is the NetBIOS name. The same is done with the Monitor Database and Site Database.

New-XDSite -DatabaseServer $env:COMPUTERNAME -LoggingDatabaseName $LDB -MonitorDatabaseName $MDB -SiteDatabaseName $SDB -SiteName $NBN

Next, we want to setup licensing. This is pretty self-explanatory. This sets the license server and the port. IT then sets the product and edition. I generally use a generic DNS name for the licensing server, “ctxlicesnse” in this example, that points to the license server IP address. Then this sets up the product code and product edition. My example shows a setup for XenDesktop Advanced edition. You can get these variables with the following commands.

PS C:\> Get-ConfigProduct

Code                    Name
----                    ----
XDT                     XenDesktop
MPS                     XenApp
PS C:\> Get-ConfigProductEdition -ProductCode XDT
PLT
ENT
APP
ADV
STD

Here is my code to add licensing.

Set-XDLicensing -LicenseServerAddress ctxlicense -LicenseServerPort 27000
Set-ConfigSite -LicensingModel Concurrent -ProductCode XDT -ProductEdition ADV

Next, we setup a Machine catalog. This assumes that we already have at least one VDA setup, already pointed to this Delivery Controller. This is a simple persistent desktop Machine Catalog. This is setup for XenApp type Full Desktops (MultiSession), and we add the first XD box to the Machine Catalog.

New-BrokerCatalog -SessionSupport MultiSession -ProvisioningType Manual -AllocationType Random -Name 2012R2 -Description 2012R2 -PersistUserChanges OnLocal -MachinesArePhysical $true
New-BrokerMachine -MachineName TEST-RDU-XD-01 -CatalogUid 1

This next bit of code sets up the Desktop Group. This code snip was taken from Aaron Parker’s blog here and edited for my using. I encourage you to read his blog page to understand what this stuff does. It is much more involved than my code, and does a great job setting up the delivery group. I will try to break down the piece for all of us though. Let’s walk through the variables first.

$XDC = $env:COMPUTERNAME
$assignedGroup = "$NBN`\$NBN`_CTX_Desktop"
$desktopGroupName = "Some Desktop Group"
$desktopGroupPublishedName = "Some Desktop Group"
$desktopGroupDesc = "Some Desktop Group"
$colorDepth = 'TwentyFourBit'
$deliveryType = 'DesktopsandApps'
$desktopKind = 'Shared'
$sessionSupport = "MultiSession"
$functionalLevel = 'L7_6'
$timeZone = 'EST Eastern Standard Time'
$offPeakBuffer = 10
$peakBuffer = 10
$machineCatalogName = "2012R2"

You can set a TON of stuff in here, and it can get complicated when you are doing MCS/PVS and stuff. In this example, we are setting up a XenApp Full Desktop to the Machine Catalog we created above (2012R2). $deliverytime can be DesktopsOnly, DesktopsandApps, or AppsOnly. $deliverykind can be Shared or Private. $sessionSupport can be MultiSession or SingleSession. This is a new environment, with all 7.6, so we set the $functionalLevel to L7_6. This can be set to 5, 7, or 7.6. There are so many other commands in here that Aaron has detailed; I won’t go into them here. The command I have used for this example is below.

New-BrokerDesktopGroup -ErrorAction SilentlyContinue -AdminAddress $XDC -Name $desktopGroupName -DesktopKind $desktopKind -DeliveryType $deliveryType -Description $desktopGroupPublishedName -PublishedName $desktopGroupPublishedName -MinimumFunctionalLevel $functionalLevel -ColorDepth $colorDepth -SessionSupport $sessionSupport -InMaintenanceMode $False -IsRemotePC $False -SecureIcaRequired $False -Scope @()

The next thing we need to do is to add machines to the desktop group.

$machineCatalog = Get-BrokerCatalog -AdminAddress $XDC -Name $machineCatalogName
Add-BrokerMachinesToDesktopGroup -AdminAddress $XDC -Catalog $machineCatalog -Count $machineCatalog.UnassignedCount -DesktopGroup $desktopGroup

Then we want to add users to the desktop group

$brokerUsers = New-BrokerUser -AdminAddress $XDC -Name $assignedGroup
New-BrokerEntitlementPolicyRule -AdminAddress $XDC -Name ($desktopGroupName + "_" + $Num.ToString()) -IncludedUsers $brokerUsers -DesktopGroupUid $desktopGroup.Uid -Enabled $True -IncludedUserFilterEnabled $False

Lastly, we want to allow access through Access Gateway

New-BrokerAccessPolicyRule -AdminAddress $XDC -Name $accessPolicyRule -IncludedUsers @($brokerUsers.Name) -AllowedConnections 'ViaAG' -AllowedProtocols @('HDX','RDP') -AllowRestart $True -DesktopGroupUid $desktopGroup.Uid -Enabled $True -IncludedSmartAccessFilterEnabled $True -IncludedSmartAccessTags @() -IncludedUserFilterEnabled $True

Here is the full script from start to finish.

add-pssnapin c*
$XDC = $env:COMPUTERNAME
$nbn = $env:USERDOMAIN
$assignedGroup = "$NBN`\$NBN`_CTX_Desktop"
$LDB = "CitrixConfigLogging" + $nbn
$MDB = "CitrixMonitor" + $nbn
$SDB = "Citrix" + $nbn

# Desktop Group properties
$desktopGroupName = "Some Desktop Group"
$desktopGroupPublishedName = "Some Desktop Group"
$desktopGroupDesc = "Some Desktop Group"
$colorDepth = 'TwentyFourBit'
$deliveryType = 'DesktopsandApps'
$desktopKind = 'Shared'
$sessionSupport = "MultiSession"
$functionalLevel = 'L7_6'
$timeZone = 'EST Eastern Standard Time'
$offPeakBuffer = 10
$peakBuffer = 10
$machineCatalogName = "2012R2"

write-host "Creating Citrix Databases"
New-XDDatabase -AllDefaultDatabases -DatabaseServer $env:COMPUTERNAME -SiteName $NBN

write-host "Setting up Site"
New-XDSite -DatabaseServer $env:COMPUTERNAME -LoggingDatabaseName $LDB -MonitorDatabaseName $MDB -SiteDatabaseName $SDB -SiteName $NBN

write-host "Setting up licensing"
Set-XDLicensing -LicenseServerAddress ctxlicense -LicenseServerPort 27000
Set-ConfigSite -LicensingModel Concurrent -ProductCode XDT -ProductEdition ADV

write-host "Setting up Machine Catalog"
New-BrokerCatalog -SessionSupport MultiSession -ProvisioningType Manual -AllocationType Random -Name 2012R2 -Description 2012R2 -PersistUserChanges OnLocal -MachinesArePhysical $true
New-BrokerMachine -MachineName TEST-RDU-XD-01 -CatalogUid 1

$VerbosePreference = "Continue"

write-host "Creating Desktop Group"

If (!(Get-BrokerDesktopGroup -Name $desktopGroupName -ErrorAction SilentlyContinue)) {
Write-Verbose "Creating new Desktop Group: $desktopGroupName"
$desktopGroup = New-BrokerDesktopGroup -ErrorAction SilentlyContinue -AdminAddress $XDC -Name $desktopGroupName -DesktopKind $desktopKind -DeliveryType $deliveryType -Description $desktopGroupPublishedName -PublishedName $desktopGroupPublishedName -MinimumFunctionalLevel $functionalLevel -ColorDepth $colorDepth -SessionSupport $sessionSupport -InMaintenanceMode $False -IsRemotePC $False -SecureIcaRequired $False -Scope @()
}
If ($desktopGroup) {

Write-Verbose "Getting details for the Machine Catalog: $machineCatalogName"
$machineCatalog = Get-BrokerCatalog -AdminAddress $XDC -Name $machineCatalogName
Write-Verbose "Adding $machineCatalog.UnassignedCount machines to the Desktop Group: $desktopGroupName"
$machinesCount = Add-BrokerMachinesToDesktopGroup -AdminAddress $XDC -Catalog $machineCatalog -Count $machineCatalog.UnassignedCount -DesktopGroup $desktopGroup

Write-Verbose "Creating user/group object in the broker for $assignedGroup"
If (!(Get-BrokerUser -AdminAddress $XDC -Name $assignedGroup -ErrorAction SilentlyContinue)) {
$brokerUsers = New-BrokerUser -AdminAddress $XDC -Name $assignedGroup
} Else {
$brokerUsers = Get-BrokerUser -AdminAddress $XDC -Name $assignedGroup
}

$Num = 1
Do {
$Test = Test-BrokerEntitlementPolicyRuleNameAvailable -AdminAddress $XDC -Name @($desktopGroupName + "_" + $Num.ToString()) -ErrorAction SilentlyContinue
If ($Test.Available -eq $False) { $Num = $Num + 1 }
} While ($Test.Available -eq $False)
Write-Verbose "Assigning $brokerUsers.Name to Desktop Catalog: $machineCatalogName"
$EntPolicyRule = New-BrokerEntitlementPolicyRule -AdminAddress $XDC -Name ($desktopGroupName + "_" + $Num.ToString()) -IncludedUsers $brokerUsers -DesktopGroupUid $desktopGroup.Uid -Enabled $True -IncludedUserFilterEnabled $False

# Check whether access rules exist and then create rules for direct access and via Access Gateway
$accessPolicyRule = $desktopGroupName + "_Direct"
If (Test-BrokerAccessPolicyRuleNameAvailable -AdminAddress $XDC -Name @($accessPolicyRule) -ErrorAction SilentlyContinue) {
Write-Verbose "Allowing direct access rule to the Desktop Catalog: $machineCatalogName"
New-BrokerAccessPolicyRule -AdminAddress $XDC -Name $accessPolicyRule -IncludedUsers @($brokerUsers.Name) -AllowedConnections 'NotViaAG' -AllowedProtocols @('HDX','RDP') -AllowRestart $True -DesktopGroupUid $desktopGroup.Uid -Enabled $True -IncludedSmartAccessFilterEnabled $True -IncludedUserFilterEnabled $True
} Else {
Write-Error "Failed to add direct access rule $accessPolicyRule. It already exists."
}
$accessPolicyRule = $desktopGroupName + "_AG"
If (Test-BrokerAccessPolicyRuleNameAvailable -AdminAddress $XDC -Name @($accessPolicyRule) -ErrorAction SilentlyContinue) {
Write-Verbose "Allowing access via Access Gateway rule to the Desktop Catalog: $machineCatalogName"
New-BrokerAccessPolicyRule -AdminAddress $XDC -Name $accessPolicyRule -IncludedUsers @($brokerUsers.Name) -AllowedConnections 'ViaAG' -AllowedProtocols @('HDX','RDP') -AllowRestart $True -DesktopGroupUid $desktopGroup.Uid -Enabled $True -IncludedSmartAccessFilterEnabled $True -IncludedSmartAccessTags @() -IncludedUserFilterEnabled $True
} Else {
Write-Error "Failed to add Access Gateway rule $accessPolicyRule. It already exists."
}

} #End If DesktopGroup

Installing Storefront

This portion of the scripting is going to do a bunch of things. It will install the pre-requisites for Storefront, including IIS. It installs Storefront. It imports a certificate and binds it to the default website. The sets up the initial Storefront base URL then finishes the configuration. The first thing I did was to copy the 3.x version of CitrixStoreFront-x64 into my share to the x64\StoreFront directory and overwrite the default one. Luckily, this works so we can use XenDesktopServerSetup.exe again to install it.

The first thing we are going to do is install the pre-requisites and install Storefront. Again, I am just going to do a net-use to my share and run everything.

net use --% x: \\10.56.90.20\XA76
Import-Module ServerManager
write-host "Installing Storefront Prereqs"
Add-WindowsFeature AS-Net-Framework,Web-Net-Ext45,Web-AppInit,Web-ASP-Net45,Web-ISAPI-Ext,Web-ISAPI-Filter,Web-Default-Doc,Web-HTTP-Errors,Web-Static-Content,Web-HTTP-Redirect,Web-HTTP-Logging,Web-Filtering,Web-Windows-Auth,Web-Client-Auth
x:
cd '.\x64\XenDesktop Setup'
write-host "Installing Storefront"
.\XenDesktopServerSetup.exe --% /components STOREFRONT /NOSQL /quiet

Next, we want to import the certificate and bind it to the default web site. First, we ask for the cert password.

$myPW = read-host -Prompt "Enter Cert Password here"

We then want to import the certificate and assign it to the default website. Copy the .pfx file to the root of C:\. You will need the thumbprint of the certificate to put in the XXXXXXXXXXXXXXXXXX location of the script.

$certpw = ConvertTo-SecureString -String $myPW -Force -AsPlainText
Import-PfxCertificate -filepath "C:\cert.pfx" Cert:\LocalMachine\My -Password $certpw
New-WebBinding -Name "Default Web Site" -IPAddress "*" -Port 443 -Protocol https
cd IIS:
cd .\SSLBindings
Get-Item Cert:\LocalMachine\My\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | new-item 0.0.0.0!443
c:

Storefront Configuration

This gets us all set to start configuring Storefront. We will first need to import the Storefront PowerShell modules.

. "C:\Program Files\Citrix\Receiver StoreFront\Scripts\ImportModules.ps1"

Let us take a look at some of the variables we will be using here.

$nbn = $env:USERDOMAIN
$GatewayAddress = "https://site.domain.com"
$Farmname = "XenApp76 Farm"
$Port = "80"
$TransportType = "HTTP"
$sslRelayPort = "443"
$LoadBalance = $false
$FarmType = "XenDesktop"
$fqdn = "$env:computername.$env:userdnsdomain"
$baseurl = "https://" + $fqdn
$SFPath = "/Citrix/" + $nbn.toLower()
$SFPathWeb = "$SFPath`Web"
$SFPathDA = "$SFPath`DesktopAppliance"
$GatewayName = "TEST-RDU-NS-01"
$staservers = "http://" + $fqdn + "/scripts/ctxsta.dll"
$snipIP = "10.56.13.9"

Again, keep in mind this is a small environment, so we will be using a single server for the XDC/Storefront roles. My $baseurl variable will resolve to https://server.domain.local. I set the store name to $nbn (NetBIOS Name), in this example it is TEST. Then using some simple PowerShell I set $SFPath, $SFPathWeb, and $SFPathDA to /Citrix/test, /Citrix/testWeb, and /Citrix/testDesktopAppliance respectively. You can set these variables as appropriate for your environment. The first command we want to run will do the initial configuration of Storefront.

Set-DSInitialConfiguration -hostBaseUrl $baseurl -farmName $Farmname -port $Port -transportType $TransportType -sslRelayPort $sslRelayPort -servers $fqdn -loadBalance $LoadBalance -farmType $FarmType -StoreFriendlyName TEST -StoreVirtualPath $SFPath -WebReceiverVirtualPath $SFPathWeb -DesktopApplianceVirtualPath $SFPathDA

The next thing I do here is setup the beacons. I set this up now because if you setup the gateway first, it sets the $baseurl as an external beacon. In my configuration, I do NOT want $baseurl to be an external beacon. At the time of writing this blog, Citrix has not written the full documentation for these PowerShell modules. I have already put in an RFE to get these up on Citrix’s site. That being said, I was not able to figure out HOW to remove an external beacon. The gateway module detects if you have any external beacons configured. If it detects none are configured, it automatically makes $baseurl and www.citrix.com the two external beacons. Setting up the external beacons is as simple as these commands.

$beaconID = ([guid]::NewGuid()).ToString()
Add-DSGlobalExternalBeacon -BeaconID $beaconID -BeaconAddress http://www.google.com
$beaconID = ([guid]::NewGuid()).ToString()
Add-DSGlobalExternalBeacon -BeaconID $beaconID -BeaconAddress http://www.citrix.com

Next, we are going to add a NetScaler gateway to the configuration. Reference the variables above. Not too much complicated in this command. This box is also the XDC, so the STA setup simply points to http://server.domain.local/scripts/ctxsta.dll.

$GatewayID = ([guid]::NewGuid()).ToString()
Add-DSGlobalV10Gateway -Id $GatewayID -Name $GatewayName -Address $GatewayAddress -Logon Domain -IPAddress $snipIP -SessionReliability $false -SecureTicketAuthorityUrls $staservers -IsDefault $true

The previous command creates the NetScaler gateway. We then have to enable NetScaler authentication and link this gateway to the store.

$gateway = Get-DSGlobalGateway -GatewayId $GatewayID
Set-DSStoreGateways -SiteId 1 -VirtualPath $SFPath -Gateways $gateway
Set-DSStoreRemoteAccess -SiteId 1 -VirtualPath $SFPath -RemoteAccessType "StoresOnly"
Add-DSAuthenticationProtocolsDeployed -SiteId 1 -VirtualPath /Citrix/Authentication -Protocols CitrixAGBasic
Set-DSWebReceiverAuthenticationMethods -SiteId 1 -VirtualPath $SFPathWeb -AuthenticationMethods ExplicitForms,CitrixAGBasic

This next command was from the Eric’s blog referenced above. This disables the check publisher’s certificate revocation to speed up console start-up

set-ItemProperty -path "REGISTRY::\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\WinTrust\Trust Providers\Software Publishing\" -name State -value 146944

Lastly we as we are using $fqdn as $baseurl, we will want to turn the loopback to OnUsingHTTP because the certificate is not going to match. You can look at more details on this command here.

Set-DSLoopback -SiteId 1 -VirtualPath $SFPathWeb -Loopback OnUsingHttp

There we have it. Storefront configuration is DONE, dude! All we need to do is setup an internal DNS cname to point site.domain.com to server.domain.local and we have single URL for internal/external access to your XenDesktop 7.6 environment.

Full Script is below.

# Certificate Password
#==================
$myPW = read-host -Prompt "Enter Cert Password here"

# StoreFront Parameters
#==================
$nbn = $env:USERDOMAIN
$GatewayAddress = "https://site.domain.com"
$Farmname = "XenApp76 Farm"
$Port = "80"
$TransportType = "HTTP"
$sslRelayPort = "443"
$LoadBalance = $false
$FarmType = "XenDesktop"
$fqdn = "$env:computername.$env:userdnsdomain"
$baseurl = "https://" + $fqdn
$SFPath = "/Citrix/" + $nbn.toLower()
$SFPathWeb = "$SFPath`Web"
$SFPathDA = "$SFPath`DesktopAppliance"
$GatewayName = "TEST-RDU-NS-01"
$staservers = "http://" + $fqdn + "/scripts/ctxsta.dll"
$snipIP = "10.56.13.9"

#write-host "Mapping Drive"
net use --% x: \\10.56.90.20\XA76
Import-Module ServerManager

write-host "Installing Storefront Prereqs"
Add-WindowsFeature AS-Net-Framework,Web-Net-Ext45,Web-AppInit,Web-ASP-Net45,Web-ISAPI-Ext,Web-ISAPI-Filter,Web-Default-Doc,Web-HTTP-Errors,Web-Static-Content,Web-HTTP-Redirect,Web-HTTP-Logging,Web-Filtering,Web-Windows-Auth,Web-Client-Auth
x:
cd '.\x64\XenDesktop Setup'

write-host "Installing Storefront"
.\XenDesktopServerSetup.exe --% /components STOREFRONT /NOSQL /quiet

#write-host "Copy certificate to C:\ before moving on"
#pause

write-host "Installing Certificate"
$certpw = ConvertTo-SecureString -String $myPW -Force -AsPlainText
Import-PfxCertificate -filepath "C:\wildcard.vc3advantage.com-NEW.pfx" Cert:\LocalMachine\My -Password $certpw
New-WebBinding -Name "Default Web Site" -IPAddress "*" -Port 443 -Protocol https
cd IIS:
cd .\SSLBindings
Get-Item Cert:\LocalMachine\My\8CE850C9DCD7C26DF8E8FD4C44BF7D9E586E8AD1 | new-item 0.0.0.0!443
c:

# Import Storefront module
#==========================
write-host "Installing Storefront Modules"
. "C:\Program Files\Citrix\Receiver StoreFront\Scripts\ImportModules.ps1"

# Setup Initial Configuration
#============================
write-host "Initial Storefront Configuration"
Set-DSInitialConfiguration -hostBaseUrl $baseurl -farmName $Farmname -port $Port -transportType $TransportType -sslRelayPort $sslRelayPort -servers $fqdn -loadBalance $LoadBalance -farmType $FarmType -StoreFriendlyName TEST -StoreVirtualPath $SFPath -WebReceiverVirtualPath $SFPathWeb -DesktopApplianceVirtualPath $SFPathDA

write-host "Configuring Beacons"
$beaconID = ([guid]::NewGuid()).ToString()
Add-DSGlobalExternalBeacon -BeaconID $beaconID -BeaconAddress http://www.google.com
$beaconID = ([guid]::NewGuid()).ToString()
Add-DSGlobalExternalBeacon -BeaconID $beaconID -BeaconAddress http://www.citrix.com

$GatewayID = ([guid]::NewGuid()).ToString()
Add-DSGlobalV10Gateway -Id $GatewayID -Name $GatewayName -Address $GatewayAddress -Logon Domain -IPAddress $snipIP -SessionReliability $false -SecureTicketAuthorityUrls $staservers -IsDefault $true
$gateway = Get-DSGlobalGateway -GatewayId $GatewayID
Set-DSStoreGateways -SiteId 1 -VirtualPath "/Citrix/test" -Gateways $gateway
Set-DSStoreRemoteAccess -SiteId 1 -VirtualPath /Citrix/test -RemoteAccessType "StoresOnly"
Add-DSAuthenticationProtocolsDeployed -SiteId 1 -VirtualPath /Citrix/Authentication -Protocols CitrixAGBasic

write-host "Disable check publisher's cert revocation"
set-ItemProperty -path "REGISTRY::\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\WinTrust\Trust Providers\Software Publishing\" -name State -value 146944

write-host "Setting Loopback to OnUsingHttp"
Set-DSLoopback -SiteId 1 -VirtualPath $SFPathWeb -Loopback OnUsingHttp
Oct 192015
 

Sacha Thomet who was one of my competitors for the “Geekovation” contest at Synergy (he won!) wrote a PVS documentation script (http://blog.appcloud.ch/citrix-pvs-healthcheck/).  He tweeted a revision, and after looking at the code I decided to try my hand at it.  I have to give credit to Remko Weijnen (http://www.remkoweijnen.nl/blog/2012/02/29/convert-mcli-output-into-powershell-objects/) for the code to change the mcli text output into arrays, and to Martin Pugh for the code to make a pretty web report (website and info in the comments of the function).

The script requires the mcli pssnapin (https://www.citrix.com/blogs/2011/01/11/pvs-powershell-mclipssnapin/)
Here is the script http://pastebin.com/p5qBAseZ 
Edit line 131 to set the path to the html output file.
Add “invoke-expression $htm” at the end if you want it to auto launch the html report.

Feel free to edit the script – the html output is kinda dirty but it works.

pvs report

Aug 312015
 

Another Netscaler – Powershell script leveraging Nitro!
This script will create a Cipher Group with all the right Cipher Suites (depending on VPX\MPX), or let you select one you have already created, and assign it to any ssl vserver.  NOTE:  Very important!!  If you do this to a XD/XA gateway – all users connected through that gateway will be disconnected!!  (they can of course just re-connect).  Below is a video from my test environment.  You will see that “TestCGN” Cipher Group does not exist.  I create it, and then select my owa ssl vserver as the vserver to bind it to (I could have selected them all).  The link to the script is below the video – remember TEST before using it in production!

Edit: Completely slipped my mind to disable SSLv3.  I updated the script to disable SSLv3/TLS1, and enable TLS1.1/1.2.  If you have an SSL Profile set this might fail.  Fixed that too :-)… and updated the video.

Script

Aug 282015
 

This post will be short and sweet.  I just wanted to post my first attempt at Powershell scripting against Netscaler.  Click here

Read the comments!

This script will connect to your netscaler (I have it do it on a gui enabled snip instead of the nsip, but  you can do it against the nsip… again read the comments).  If the NS is in a HA pair it will report if they are up or down.  Grab all vservers – report up/down/degraded… if degraded it will tell you which service is down.  Then it grabs all the gateways – reports up or down.  Finally it looks for any ica sessions, and will report if they are using Framehawk or not (requires NS 11.0 62.10 or above).

May 072015
 

Just a quick script to grab XenDesktop 7.x licensing info… would probably work on XA 7.x as well.  As the script is written it has to be run on the licensing server itself, but you could easily rewrite it with an “Invoke-Command” to allow it to run remotely.  You could also modify it to email you when the licenses go above a certain threshold, and set it as a scheduled task.  The script just does “get” commands, so there is no risk (as it is) to your environment… go nuts.

http://pastebin.com/97C7kxHq

Mar 162015
 

UPDATE:  I found trying to run this script through the Netscaler Gateway failed due to differences in the web pages.  I re-wrote the script so it will work internal directly to StoreFront, and externally with Netscaler Gateway.  The main caveat is that the wficalib.dll doesn’t allow you to logoff the session when going through the gateway.  I simply set it to look for any wfica32 processes prior to launching the application/desktop, compare that to the processes after launch, and kill the new process (disconnecting the session).  The script identifies the webpage as a gateway if */vpn/* is in the path.  I also set it to logoff of the Storefront/Gateway page when it ends the script.  If you were to run it back to back without logging off of the page it wouldn’t find what it is looking for initially and fail (because you’d probably already be logged on).  I may re-write this in the future with more functions to make it a bit shorter/cleaner, but as is it should work.
NOTE: I tested this with Netscaler 10.5… it may not work with previous versions as is, but if you read the script you should be able to figure out what needs to be changed.

I need to give credit to this Citrix blog post for getting me started.  This script will launch an application or desktop as a user you specify from the StoreFront web page, then send you an email to let you know if it was successful or not.

Variables you should edit:

In the send-results function
$smtpserver (line 19)
$msg.From (line 28)
$msg.to.Add (line 29)

In the main script (be sure to read the comments)
$username (line 84)
$passstring (line 86)
$resource (line 92)
$mask (line 94)
$wait (line 96)
$internetexplorer.visible (line 98)
$internetexplorer.navigate2 (line 99)

Requirements:
Powershell (x86)! – otherwise you cannot tie into the x86 dll for Receiver
(If you are going to the Netscaler Gateway it doesn’t matter which version as we won’t tie into the dll in that case)

Add the following to the registry if you are pointing to the internal StoreFront url – if you are pointing to the Netscaler Gateway it won’t matter
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Citrix\ICA Client\CCM]
“AllowLiveMonitoring”=dword:00000001
“AllowSimulationAPI”=dword:00000001

If you are running on an x86 machine change the registry path to exclude Wow6432Node, and change the path of the .dll on line 156 of the script to the correct path of the wficalib.dll.

Here is the script!

Contact me in in the channel David62277

Dec 192014
 

Like the title says this is for a very specific use case which I have run into, so definitely not for everyone.

Background

The company I work for plans on switching the user home directories to a DFS path in order to accommodate our cloud DR solution. We currently redirect folders via Citrix policies in XenDesktop to the user home directories (ie: \\server\path\username). This path is to change to the DFS path as well, so when a user logs onto the cloud Desktop as a Service (DaaS) desktop their folders are redirected like normal. The new path will be \\domain\dfspath\userpath\username, but it is really the same place as before (2 different paths pointing to the same place). If you change the Citrix policy to redirect the folders to \\domain\dfspath\userpath from \\server\path it will copy the contents of the old path to the new (which really does nothing because it is already there), BUT then it will delete it from the old path (since the “old” and “new” path are really the same the redirected folders are deleted)!!! Using good ol’ Microsoft redirection policies you can set the policy to not move data, but with Citrix policies I don’t see this option.

Basically, if I switch the policy to point the redirected folders to the new DFS path, a user logs on, and all their redirected folders go *POOF*. In our environment that’s pretty much everything but AppData.

I screwed around with the registry a bit and found if you delete the values under HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders and User Shell Folders that point to the share along with the History key under HKCU:\Software\Citrix\UserProfileManager\FolderRedirection, the folders are redirected fine after the DFS path is in place with no problems.

 

The Script

My choices after finding the work around were to:

  1. Go the easy route – Delete all the current profiles
    1. Obviously that won’t fly
  2. Load each NTUSER.DAT file into the registry one at a time and edit them
    1. With so many profiles that would take forever
    2. Would need a very long window as I couldn’t have users logging on/off hosing my work and their redirected folders
  3. Get a window to disable logons to our XD environment, get everyone logged off, and use a script to load every ntuser.dat file/edit/unload
    1. Only need a short window

I chose option 3 and got to work on a script below. Read the comments between the <# #> marks!  As always test test test…

Your best bet is to copy the script below into your favorite powershell editor it doesn’t format very nicely here… I just use powershell_ise.exe

 

 

$start = Get-Date <# Get the start time to calculate how long the script took at the end #>
<# we get the folders under the root of the profile store (full names), for each append the path to where the NTUSER.DAT would be found, and save that as the $profiles variable. THIS WILL ALMOST CERTAINLY BE DIFFERENT FOR YOU, SO FIGURE OUT YOUR PATH #>
$profiles = gci \\server\profiles | ?{$_.psiscontainer -eq $true} |select -expand fullname | sort | %{Join-Path $_ "v2x64\UPM_Profile\NTUSER.DAT"}
<# This is where the fun starts. If you want to test this against a single test user profile comment out the $profile variable above with a pound sign before it, and make a new line like this: $profiles = "\\server\profiles\testuser\upm_profile\ntuser.dat" #>
foreach ($profile in $profiles) {
<# We check to see if the NTUSER.DAT file exists... if not it continues on to the next profile in the list #>
if ((Test-Path $profile) -eq $false) {continue}
<# This checks for completed jobs and outputs the messages to the screen (job starts in a sec). Then removes the completed job #>
if ((Get-Job -State Completed).count -gt 0) {
$jobs = Get-Job -State Completed
$jobs | Receive-Job
$jobs | Remove-Job
}
<# This sets the max running jobs to 10 - adjust this at your own risk - ie: if you change the 10 to 100 it will load 100 NTUSER.DAT files into the registry at a time ... that could be bad resource wise #>
while ((Get-Job -State Running).count -ge 10) {
Start-Sleep -s 1
}
<# The hive variable below is important... this is the name the NTUSER.DAT hive will get in HKLM. YOU WILL HAVE TO PLAY WITH THIS SO YOU GET IT RIGHT IN YOUR ENVIRONMENT. in my case it will name the hive username_temp - ie: user1_temp if the username is user1 #>
$hive = (($profile -split ".domain") -split "\\")[2] + "_temp"
<# Here we start the job - jobs run a separate instance of powershell, so it will load multiple hives. In this case there will be 10 hives loaded into the registry at a time (DO NOT OPEN REGEDIT WHILE THIS SCRIPT IS RUNNING... IT WILL STOP THE HIVES FROM UNLOADING) #>
Start-Job -name $hive {param($profile,$hive)
<# Here we start reg.exe with arguments to load the user hive, and saves the process info as $load.  Then it checks the exitcode so it can tell you if it loaded
or not#>
$load = start-process -passthru -filepath reg.exe -argumentlist "load HKLM\$hive $profile" -Wait -WindowStyle Hidden
if ($load.ExitCode -ne "0") {
Write-Host "$profile could not be loaded" -f Red
continue
} else {
Write-Host "$profile loaded"
}
<# $change variable comes into play in a sec... default is 0 #>
$change = "0"
<# Here we set the location to the registry path where the shell folders and user shell folders keys live #>
Push-Location
Set-Location hklm:\$hive\Software\Microsoft\windows\CurrentVersion\Explorer
<# We search for any key which has *shell* in the name (shell folders and user shell folders).  Then we get the key path of those keys, and all the properties
under those keys, and look for any values that are pointing to the server where the current share is hosted.  Once we have those we remove the property, and set
$change to 1... indicating that the shell folders were modified#>
gci .\ | ?{($_.psiscontainer -eq $true) -and ($_.name -like "*shell*")} | %{
$regpath = $_.pspath
$properties = $_.property
foreach ($property in $properties) {
$val = (Get-ItemProperty $regpath).$property
<# edit your server name here  #>
if ($val -like "*server*") {
$change = "1"
Remove-ItemProperty -Path $regpath -Name $property
}
}
}
<# If $change is 1 then we remove the folder redirection "history" key under the Citrix path #>
if ($change -eq "1") {
Set-Location HKLM:\$hive\Software\Citrix\UserProfileManager\FolderRedirection
if ((Test-Path .\History) -eq $true) {
ri .\History -Recurse -Force
}
}
<# We pop-location to leave the registry paths, do some cleanup so that we can unload the hive cleanly, and attempt to unload it.  If it fails it will try up
to 5 times to unload the hive.  And will let you know after the job runs #>
Pop-Location
gci env: | Out-Null
gci variable: | Out-Null
$att = 0
while ((Test-Path HKLM:\$hive) -and ($att -le 5)) {
[gc]::Collect()
$unload = start-process -PassThru -FilePath reg.exe -ArgumentList "unload HKLM\$hive" -Wait -WindowStyle Hidden
$att += 1
}
if ($unload.ExitCode -ne "0") {
Write-Host "Unable to unload $profile" -f Red
} else {
Write-Host "$profile successfully unloaded"
}
<# This cleans up the NTUSER.LOG files that get created when the hive is loaded #>
gci (Split-Path $profile -Parent) -Force | ?{($_.psiscontainer -eq $false) -and ($_.Name -like "ntuser*") -and ($_.name -ne "ntuser.dat") -and ($_.name -ne "ntuser.ini") -and ($_.Name -ne "ntuser.pol")} | ri -Force
} -ArgumentList $profile,$hive | Out-Null
}
Get-Job | Wait-Job | Receive-Job
Get-Job | Remove-Job
$end = Get-Date
$minutes = ($end - $start).Minutes
$seconds = ($end - $start).Seconds
Write-Host "Total run time $minutes minutes $seconds seconds."

Oct 312014
 

I have seen where PVS targets (mainly Desktop OS) will fail to activate via KMS after booting, and/or not get the proper group policy settings.  I think this is because PVS hasn’t released the network when Windows is trying to activate/update gpo (or something along those lines).  On top of this in my environment I have PvD and Random desktops booting off of the same vdisk image.  To fix this I created the script below, and setup a scheduled task to run at startup (using SYSTEM account).

Note: Using this script you can do a lot more than just slmgr /ato and gpupdate /force commands.  For instance if you have an antivirus service that you just want to start if the vdisk is in standard mode… you could just add a “start-service” command (of course you’d want that service to be set to manual).  Feel free to edit however it suits your environment.

Steps to implement:

  1. Start a maintenance version of your vdisk
  2. Logon to that desktop/server
  3. Open powershell_ise, or notepad
    1. Copy the script below and paste it
    2. Edit line 2 to be the FQDN of your domain
      Example: yourdomain.com
    3. Save it (remember where you saved it) – I just save mine to the root of C:\ to keep it simple
  4. Open Task Scheduler
    1. Right click on “Task Scheduler Library” and select “Create a Basic Task”
    2. Name your task and optionally add a description and click Next
    3. On the Task Trigger screen select “When the computer starts” and click Next
    4. On the Action screen click Next (Start a program should be selected by default)
    5. On the Start a Program Screen
      1. Type the path, or browse to powershell.exe (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe)
      2. in the Add arguments section:
        1. -executionpolicy unrestricted -file <path to the .ps1 file you just saved>
          Example: -executionpolicy unrestricted -file c:\startup.ps1
    6. Click Next
    7. Check “Open the Properties dialog for this task when I click Finish” and click Finish
    8. On the properties page click Change User or Group
      1. In the Select User or Group box type in “system” (no quotes) in the box and hit OK
      2. You should now see “NT AUTHORITY\SYSTEM” as the user account to run as
    9. Check the Run with highest privileges box, and click OK
  5. Perform any cleanup operations you typically do, run PvD inventory (if you use PvD), and shutdown the machine
  6. Place your vdisk into Test mode, and test away
  7. When satisfied set the vdisk to production


function startup {
while ((Test-Connection "fqdn of your domain ie: contoso.com" -count 1) -eq $null) {
Start-Sleep -Milliseconds 500
}
& cscript.exe c:\windows\system32\slmgr.vbs /ato
& gpupdate.exe /force
}
$p = gc c:\personality.ini
$r = (Get-ItemProperty registry::'HKLM\SOFTWARE\Citrix\personal vDisk\config').vhdmountpoint
if (($r -eq $null) -and (($p -like "*diskmode=p*") -or ($p -like "*writecachetype=0*"))) {
break
}
startup

Explanation of the script:

When executed it will get the content of c:\personality.ini and the value of REG_SZ vhdmountpoint.  If personality.ini contains diskmode=p or writecachetype=0 and vhdmountpoint value is blank/non-existent it will stop the script (this indicates the vdisk is in private or maintenance mode).

PvD – value of vhdmountpoint will not be blank, so even if for whatever reason the .ini file shows the disk in private/maintenance it will go on and run the function
Shared Random – value of vhdmountpoint will be blank, but the .ini should show diskmode=s and writecachetype=something other than 0 (depends on the mode), so it will also run the function.

If the break condition is not met (indicating the disk is in shared mode) then it will run the startup function.  This function tries to ping the fqdn of your domain 1 time.  If it gets a reply it will run the activation command, and gpupdate.  If it does not, it will wait half a second and try again… over and over until it gets a reply from the fqdn of your domain.

 

Sep 252014
 

A while back I wrote a script to quickly update a XenServer host or pool with all the hotfixes placed in a directory. I got tired of doing it through the gui, and having to do each update one at a time… waiting for reboots in-between.
This script will look for .xsupdate files in the directory you specify in the script. Each one will be uploaded to the pool master (or single host), and applied. After that it will reboot each XenServer one at a time.
The reboot process will disable HA and WLB (if you have it) – otherwise it will just throw an error and continue. Then one at a time starting with the pool master they will switch to maintenance mode, migrate vms off, and reboot. Once the host that rebooted is back up and enabled it will move to the next.  At the end it will re-enable HA and WLB.

The only requirement is that you have XenCenter installed on your workstation (and of course powershell with an execution policy that allows scripts to run).

The only “issue” is the last host to reboot will remain without VMs until you move them back manually, or a load balancing function moves them.

As always… test before trying this in a production environment.

function checkconnect {
Write-Host "Waiting for $item to reboot and exit maintenance mode."
$check = &$xe -s $master -u root -pw $pass host-list name-label=$item params=enabled --minimal
if ($check -eq $true) {
write-host "$item is online"
return
} else {
Start-Sleep -s 10
checkconnect
}
}

$xe = “c:\program files (x86)\citrix\xencenter\xe.exe” # if a x86 machine it will be c:\program files
$pass = “ROOT PASSWORD” # password to connect to the pool master
$master = “IP or Hostname” # ipaddress or hostname of the pool master
$patchpath = “C:\XSUPDATES” # path to the .xsupdate file(s)
$patches = gci $patchpath | where {$_.name -like “*.xsupdate”} | select -expand fullname
$pool = &$xe host-list -s $master -u root -pw $pass params=name-label –minimal
$puuid = &$xe -s $master -u root -pw $pass pool-list –minimal
$hosts = $pool -split “,”
foreach ($patch in $patches) {
$uuid = &$xe patch-upload -s $master -u root -pw $pass file-name=$patch
&$xe patch-pool-apply -s $master -u root -pw $pass uuid=$uuid
}
write-host “Disabling HA and WLB.”
&$xe -s $master -u root -pw $pass pool-param-set wlb-enabled=false uuid=$puuid
&$xe -s $master -u root -pw $pass pool-ha-disable
foreach ($item in $hosts) {
$hostuuid = &$xe -s $master -u root -pw $pass host-list name-label=$item –minimal
write-host “Placing $item in maintenance mode.”
&$xe -s $master -u root -pw $pass host-disable uuid=$hostuuid
write-host “Migrating VMs off of $item”
&$xe -s $master -u root -pw $pass host-evacuate uuid=$hostuuid
write-host “Rebooting $item”
&$xe -s $master -u root -pw $pass host-reboot host=$item –force
checkconnect
}
write-host “Enabling HA and WLB.”
&$xe -s $master -u root -pw $pass pool-ha-enable
&$xe -s $master -u root -pw $pass pool-param-set wlb-enabled=true uuid=$puuid
write-host “Pool update complete.”