Categories
Citrix Powershell

Citrix Migration – Powershell

This PowerShell script saved me hours of manual work in a project to migrate hundreds of apps to a new farm that follows a different naming convention.

Background story (that you can safely ignore)

I work in the Infrastructure team in a company that is headquartered in Geneva, Switzerland.   I will spare you with the boring details.  In short, we have setup an environment on Azure and have a Citrix farm there for over a year.  This farm hosted hundred of applications to different locations in the world.  Recently, a new Citrix Farm is setup in Geneva and we are asked to upgrade the OS to server 2019 (on hundreds of servers) and also move the Machine Catalogs/ Delivery Groups/ Applications over to the new Farm.  Since this is a production environment that have different maintenance windows (because the servers are scattered around the world with different timezone), we cannot do it all at once.  To save myself time doing repetitive works, I wrote a script that will ask for basic information and then copy the Machine Catalogs/ Delivery Groups/ Applications and their permissions over to the new Farm.  This has been working well and saved me hours of work if I had to do it manually.   The naming convention for the new farm is differed from the old one, if you can keep it the same, the script could be potential easier. 


Requirements:

  • You will need the proper permissions for both old and new Farms. 
  • Assuming they are in different domain, the machine that you run the script will need connectivity to both Farms.  If your script cannot reach the delivery Controller of either farm, then it will not work.
  • The machine that you run the script on will need to have Citrix PowerShell Modules installed.  I think installing Citrix Studio also gives you some of the Modules (if not all)

The Script

It will require modification for it to work in your environment.    I will post the full script first, and then provide explaining on each parts of the script.  Note that we have different naming convention between the old and new farms.  The script is not copying the name for the Catalogs / Delivery Groups but using the variable to create new catalogs/delivery groups with the new names.  If you can keep the same names, the script could be potential easier but will require a slight modification. 

Our naming convention includes the country names and the country code.  This is how we can tell each location apart from others.  If your catalog/deliveryGroup/App are always different, then this may not work for you at all. 

#Defining the variables

$NewAdminAddress = #Put in the new admin address here
$OldAdminAddress = #Put in the old admin address here
$DeploymentName = Read-Host -Prompt 'Enter the name of the deployment' #Our naming convention includes the country-3digitcode.  For example, Mexico is MEXICO-MX999
$AgencyCode = $DeploymentName.toupper().Substring($DeploymentName.length - 5, 5)
$index = $deploymentName.IndexOf("-")
$CountryName = $DeploymentName.Substring(0,$index)
$countryName2 = $CountryName.ToUpper().replace(' ','')
$DeliveryGroupName = $AgencyCode + "-AVA-" + $CountryName2 + "-DeliveryGroup"
$DeploymentName2 = "AVA-" + (Get-Culture).TextInfo.ToTitleCase($CountryName.ToLower())
$NewAdminFolder = $DeploymentName.toupper()
$CatalogName = $AgencyCode + "-AVA-" + $CountryName2
$Scope = #Put in the scope here if you have one otherwise you will need to remove all the scope parameter from below
$OldDGname = $DeploymentName + "-DeliveryGroup"
$OldDG = Get-BrokerDesktopGroup -AdminAddress $OldAdminAddress -name $oldDGname

#Defining variables that are used to create the Delivery Group Access Policy Rule.  This can probably look alot cleaner with less variables, but this worked for me.
$PolicyNameAGOld = $OldDGname + "_AG"
$PolicyNameDirectOld = $OldDGname + "_Direct"
$PolicyNameAGnew = $DeliveryGroupName + "_AG"
$PolicyNameDirectNew = $DeliveryGroupName + "_Direct"
$PolicyAGOld = Get-BrokerAccessPolicyRule -adminaddress $OldAdminAddress -name $PolicyNameAGOld
$PolicyDirectOld = Get-BrokerAccessPolicyRule -adminaddress $OldAdminAddress -name $PolicyNameDirectOld
$UserNamesAG = $PolicyAGOld.IncludedUsers.name
$AllowedConnectionsAG = $PolicyAGOld.allowedconnections
$UserNamesDirect = $PolicyDirectOld.IncludedUsers.name
$AllowedConnectionsDirect = $PolicyDirectOld.allowedconnections

#Defining variables and getting application info from the old farm
$OldFolder = Get-BrokerAdminFolder -AdminAddress $OldAdminAddress  -foldername *$AgencyCode*
$apps = Get-BrokerApplication -AdminAddress $OldAdminAddress -AdminFolderUid $OldFolder.Uid -MaxRecordCount 999999

#Creating Machine Catalog with the new name on the new farm.
New-BrokerCatalog -AdminAddress $NewAdminAddress -allocationtype "Random" -IsRemotePC $False -MachinesArePhysical $True -MinimumFunctionalLevel "L7_9" -Name $CatalogName -PersistUserChanges "OnLocal" -ProvisioningType "Manual" -scope $Scope -sessionsupport "MultiSession"


#Creating Delivery Group with the new name on the new farm.
New-BrokerDesktopGroup -AdminAddress $NewAdminAddress $deliveryGroupName -PublishedName $deliveryGroupName -DesktopKind $OldDG.desktopkind -SessionSupport $oldDG.SessionSupport -DeliveryType $OldDG.DeliveryType -scope $Scope

#get the info from the newly created Delivery Group
$NewDG = Get-BrokerDesktopGroup -adminaddress $NewAdminAddress -name $DeliveryGroupName


#Creating Broker App Entitlement Policy Rule on the New Farm.
New-BrokerAppEntitlementPolicyRule  -AdminAddress $NewAdminAddress -DesktopGroupUid $NewDG.Uid -Enabled $True -includedUserFilterEnabled $False -Name $DeliveryGroupName

#Creating Broker Access Policy Rule with a For loop to add all the correct groups to the permission.	
New-BrokerAccessPolicyRule -AdminAddress $NewAdminAddress $PolicyNameAGnew -DesktopGroupUid $NewDG.Uid -AllowedProtocols 'HDX, RDP' -AllowedConnections $AllowedConnectionsAG  -IncludedSmartAccessFilterEnabled 1 -IncludedUserFilterEnabled 1
		foreach($User in $UserNamesAG){
			[void](Set-BrokerAccessPolicyRule -AdminAddress $NewAdminAddress $PolicyNameAGnew -AddIncludedUsers $User)
		}
	
New-BrokerAccessPolicyRule -AdminAddress $NewAdminAddress $PolicyNameDirectNew -DesktopGroupUid $NewDG.Uid -AllowedProtocols 'HDX, RDP' -AllowedConnections $AllowedConnectionsDirect  -IncludedSmartAccessFilterEnabled 1 -IncludedUserFilterEnabled 1
		foreach($User in $UserNamesDirect){
			[void](Set-BrokerAccessPolicyRule -AdminAddress $NewAdminAddress $PolicyNameDirectNew -AddIncludedUsers $User)
		}

#Defining variables to get information from the new Catalog and Broker Machines that was just created.
$CatalogNew = Get-BrokerCatalog -AdminAddress $NewAdminAddress -name $CatalogName
$MachinesDG = Get-BrokerDesktop -AdminAddress $NewAdminAddress -DesktopGroupName $OldDGname

#copying and adding broker machines from old farm to the new farm and add them to the Delivery Group
	    foreach($Machine in $MachinesDG){
		$MachineName = $Machine.MachineName
		New-BrokerMachine -AdminAddress $NewAdminAddress -CatalogUID $CatalogNew.uid $MachineName
}
	    foreach($Machine in $MachinesDG){
		Add-BrokerMachine -AdminAddress $NewAdminAddress "$($Machine.MachineName)" -DesktopGroup $DeliveryGroupName
	}
#Creating Application Folder in Studio
New-BrokerAdminFolder  -AdminAddress $NewAdminAddress -FolderName $NewAdminFolder -ParentFolder "AVA"


foreach($App in $Apps){
				$AdminFolder = "AVA\" + $DeploymentName
				$ClientFolder = $DeploymentName2
				$CommandLineArguments = $App.CommandLineArguments
				$AppPath = $App.CommandLineExecutable
				$AppFolder = $App.workingdirectory
				$PublishedName = $App.PublishedName
				$Name = $App.ApplicationName
				$AppName = "AVA\" + $App.Name
				$Users = $App.associatedUsernames
				$FilterUsers = $App.UserFilterEnabled
				[void](New-BrokerApplication  -AdminAddress $NewAdminAddress -AdminFolder $AdminFolder -ApplicationType "HostedOnDesktop" -ClientFolder $ClientFolder -CommandLineArguments $CommandLineArguments -CommandLineExecutable $AppPath -CpuPriorityLevel "Normal" -DesktopGroup $DeliveryGroupName -Enabled $True -IconUid 4 -IgnoreUserHomeZone $False -MaxPerUserInstances 0 -MaxTotalInstances 0 -Name $name -Priority 0 -PublishedName $PublishedName -SecureCmdLineArgumentsEnabled $True -ShortcutAddedToDesktop $False -ShortcutAddedToStartMenu $False -UserFilterEnabled $False -Visible $True -WaitForPrinterCreation $False -WorkingDirectory $AppFolder)
				$AppNew = Get-BrokerApplication -AdminAddress $NewAdminAddress -Name $AppName -MaxRecordCount 999999
				$UidNew = $AppNew.uid
				foreach($User in $Users){
					[void](Add-BrokerUser -AdminAddress $NewAdminAddress $user -Application $uidNew)
				}
				[void](Set-BrokerApplication -AdminAddress $NewAdminAddress -InputObject $AppNew -UserFilterEnabled $FilterUsers)
		}

Explanation:

#Defining the variables

$NewAdminAddress = #Put in the new admin address here, used in establish connection to the new Farm

$OldAdminAddress = #Put in the old admin address here, used in establish connection to the old Farm

$DeploymentName = Read-Host -Prompt 'Enter the name of the deployment' #Our naming convention includes the country-countryCode+3digitcode.  For example, Mexico is MEXICO-MX999
$AgencyCode = $DeploymentName.ToUpper().Substring($DeploymentName.length - 5, 5) #this  will give us only the CountryCode+3digitcode part of the name, for example MX999
$index = $deploymentName.IndexOf("-") #This will define “-“ as an index, so we can get whatever before “-“ or after “-“ in a string

$CountryName = $deploymentName.substring(0,$index) #This will grab the CountryName from the $DeploymentName Variable.  In our example, this will get us “Mexico”

$countryName2 = $countryname.toupper().replace(' ','') #This will get the CountryName to be all Capital letters.  It will replace any “space” to no space.  If the user types in Costa Rica with the space, this will remove the space and make it CostaRica.  I added this because I gave my script to other people to use too and I have no control on what they input. ToUpper() will make it all capital letters.

$DeliveryGroupName = $AgencyCode + "-AVA-" + $CountryName2 + "-DeliveryGroup" #this will get the $agencyCode (MX999) + -AVA- + $countryName2 + -DeliveryGroup.  In our example, this will return MX999-AVA-Mexico-DeliveryGroup.  “AVA” is the name of our project (it is not real.  I change the name here.) that we want to include as part of the naming convention.

$DeploymentName2 = "AVA-" + (Get-Culture).TextInfo.ToTitleCase($CountryName.ToLower())
#this will get the countryName formatted as “Mexico” with first character being capitalized.  In case the user puts in MEXICO, this will change it back to Mexico.  ToTitleCase means only the first character is capitalized.  Again, AVA is the project name and part of our naming convention.  

$NewAdminFolder = $deploymentname.toupper()
#This is the name of the Admin Folder in Citrix Studio.  It is used for organizing the apps.

$CatalogName = $AgencyCode + "-AVA-" + $CountryName2
#this is the name of the Machine Catalog.  In our example, this will return “MX999-AVA-MEXICO”

$Scope = #Put in the scope here if you have one otherwise you will need to remove all the scope parameter from below

$OldDGname = $deploymentName + "-DeliveryGroup"  #The old naming convention was just MEXICO-MX999-DeliveryGroup.  This variable is defined to gather info from the old Delivery Group.

$OldDG = Get-BrokerDesktopGroup -AdminAddress $OldAdminAddress -name $oldDGname  #this will get the information from the old farm (using $OldAdminAddress to connect) with the name $OldDGname (in our example, the name will be MEXICO-MX999-DeliveryGroup.  We are going to paste the value from the delivery Group in the old farm to the new delivery Group created in the new farm.

#Defining variables that are used to create the Delivery Group Access Policy Rule.  This can probably look alot cleaner with less variables, but this worked for me.

$PolicyNameAGOld = $OldDGname + "_AG"  #this will return as MEXICO-MX999-DeliveyGroup_AG .  Each Delivery Group has two Access Policy Rule if you create it from the Studio Console.  One is named as [DeliveryGroup Name]_AG and the other [DeliveryGroup Name]_Direct.  If you only create one of these with powershell, you will not be able to modify the delivery group from Studio.  You will see a message saying something like “the Delivery Group was modified and cannot be accessed”

$PolicyNameDirectOld = $OldDGname + "_Direct"  #this will return as MEXICO-MX999-DeliveryGroup_Direct .  This and the above are used to grab the info from the individual Policy Access rule so we can copy them from the old Farm.

$PolicyNameAGnew = $DeliveryGroupName + "_AG"  #since we have new naming convention, we will use that new name to create the new Access policy rule.  This will return as MX999-AVA-Mexico-DeliveryGroup_AG

$PolicyNameDirectNew = $DeliveryGroupName + "_Direct"    #Same as above, but used for the Direct  policy rule.  This will return as MX999-AVA-Mexico-DeliveryGroup_Direct

$PolicyAGOld = Get-BrokerAccessPolicyRule -adminaddress $OldAdminAddress -name $PolicyNameAGOld  #this will grab all the information from the old AG policy access rule

$PolicyDirectOld = Get-BrokerAccessPolicyRule -adminaddress $OldAdminAddress -name $PolicyNameDirectOld     #this will grab all the information from the old Direct policy access rule.

$UserNamesAG = $PolicyAGOld.IncludedUsers.name   #This will gather a list of the names of all the included Users in the AG Policy access rule (that has permission to the delivery Group)

$AllowedConnectionsAG = $PolicyAGOld.allowedconnections  #this is getting the value of the Allowed Connection Field of the Direct Policy Access Rule from the old farm.

$UserNamesDirect = $PolicyDirectOld.IncludedUsers.name   #This will gather a list of the names of all the included Users in the Direct Policy access rule (that has permission to the delivery Group)

$AllowedConnectionsDirect = $PolicyDirectOld.allowedconnections   #this will obtain the value of the Allowed Connection field of the Direct Policy Access Rule from the old farm.

#Defining variables and getting application info from the old farm

$OldFolder = Get-BrokerAdminFolder -AdminAddress $OldAdminAddress  -foldername *$AgencyCode*    #this will get all the AdminFolder information from the old Farm, connected using the $OldAdminAddress, looking for the folder that is like MX999 .  “*” is a wildcard and this will return anything that is like MX999.  This won’t work if there are multiple folders will similar names.

$apps = Get-BrokerApplication -AdminAddress $OldAdminAddress -AdminFolderUid $OldFolder.Uid -MaxRecordCount 999999    #this will gather all the applications information from the old farm with the folder UID that we just gather from the above variable.  

Now that we are done will all the variables (there are a few more that we will define below.  We can define them yet because they are the new Catalog/DeliveryGroup that we have yet to create), we are ready for some actions:

#Creating Machine Catalog with the new name on the new farm.  The required fields are mostly self-explanatory.   You can compare the fields with what you see in the Machine Catalog properties in Studio

New-BrokerCatalog -AdminAddress $NewAdminAddress -allocationtype "Random" -IsRemotePC $False -MachinesArePhysical $True -MinimumFunctionalLevel "L7_9" -Name $CatalogName -PersistUserChanges "OnLocal" -ProvisioningType "Manual" -scope $Scope -sessionsupport "MultiSession"   

#Creating Delivery Group with the new name on the new farm.  This is also pretty self-explanatory.  You can also compare the properties in the DeliveryGroup from Studio.

New-BrokerDesktopGroup -AdminAddress $NewAdminAddress $deliveryGroupName -PublishedName $deliveryGroupName -DesktopKind $OldDG.desktopkind -SessionSupport $oldDG.SessionSupport -DeliveryType $OldDG.DeliveryType -scope $Scope

#get the info from the newly created Delivery Group

$NewDG = Get-BrokerDesktopGroup -adminaddress $NewAdminAddress -name $DeliveryGroupName


#Creating Broker App Entitlement Policy Rule on the New Farm.

New-BrokerAppEntitlementPolicyRule  -AdminAddress $NewAdminAddress -DesktopGroupUid $NewDG.Uid -Enabled $True -includedUserFilterEnabled $False -Name $DeliveryGroupName

#Creating Broker Access Policy Rule with a For loop to copy all the security groups from the old farm to the new farm.  Note that there are two for loop, taking care both the AG and the Direct Access Policy Rule.

New-BrokerAccessPolicyRule -AdminAddress $NewAdminAddress $PolicyNameAGnew -DesktopGroupUid $NewDG.Uid -AllowedProtocols 'HDX, RDP' -AllowedConnections $AllowedConnectionsAG  -IncludedSmartAccessFilterEnabled 1 -IncludedUserFilterEnabled 1
		foreach($User in $UserNamesAG){
			[void](Set-BrokerAccessPolicyRule -AdminAddress $NewAdminAddress $PolicyNameAGnew -AddIncludedUsers $User)
		}
	
New-BrokerAccessPolicyRule -AdminAddress $NewAdminAddress $PolicyNameDirectNew -DesktopGroupUid $NewDG.Uid -AllowedProtocols 'HDX, RDP' -AllowedConnections $AllowedConnectionsDirect  -IncludedSmartAccessFilterEnabled 1 -IncludedUserFilterEnabled 1
		foreach($User in $UserNamesDirect){
			[void](Set-BrokerAccessPolicyRule -AdminAddress $NewAdminAddress $PolicyNameDirectNew -AddIncludedUsers $User)
		}

#Defining variables to get information from the new Catalog and Broker Machines that was just created.

$CatalogNew = Get-BrokerCatalog -AdminAddress $NewAdminAddress -name $CatalogName
$MachinesDG = Get-BrokerDesktop -AdminAddress $NewAdminAddress -DesktopGroupName $OldDGname

#copying and adding broker machines from old farm to the new farm and add them to the Delivery Group.  The first For Loop will add the machines that are in the old Machine Catalog to the new Machine Catalog.  The second for loop will add the newly added machines to the new Delivery Group. 

	    foreach($Machine in $MachinesDG){
		$MachineName = $Machine.MachineName
		New-BrokerMachine -AdminAddress $NewAdminAddress -CatalogUID $CatalogNew.uid $MachineName
}
	    foreach($Machine in $MachinesDG){
		Add-BrokerMachine -AdminAddress $NewAdminAddress "$($Machine.MachineName)" -DesktopGroup $DeliveryGroupName
	}

#Creating Application Folder in Studio under the parent Folder “AVA”, you can remove -ParentFodler “AVA” if you don’t use a Parent Folder at all.  
New-BrokerAdminFolder  -AdminAddress $NewAdminAddress -FolderName $NewAdminFolder -ParentFolder "AVA"


#This For Loop will copy all the applications, as well as the permission, in this delivery Group from the old farm to the new one.  One thing that we cannot do is to assign the correct Icon for the applications because the icon id is different between the two farms.  

foreach($App in $Apps){
				$AdminFolder = "AVA\" + $DeploymentName
				$ClientFolder = $DeploymentName2
				$CommandLineArguments = $App.CommandLineArguments
				$AppPath = $App.CommandLineExecutable
				$AppFolder = $App.workingdirectory
				$PublishedName = $App.PublishedName
				$Name = $App.ApplicationName
				$AppName = "AVA\" + $App.Name
				$Users = $App.associatedUsernames
				$FilterUsers = $App.UserFilterEnabled
				[void](New-BrokerApplication  -AdminAddress $NewAdminAddress -AdminFolder $AdminFolder -ApplicationType "HostedOnDesktop" -ClientFolder $ClientFolder -CommandLineArguments $CommandLineArguments -CommandLineExecutable $AppPath -CpuPriorityLevel "Normal" -DesktopGroup $DeliveryGroupName -Enabled $True -IconUid 4 -IgnoreUserHomeZone $False -MaxPerUserInstances 0 -MaxTotalInstances 0 -Name $name -Priority 0 -PublishedName $PublishedName -SecureCmdLineArgumentsEnabled $True -ShortcutAddedToDesktop $False -ShortcutAddedToStartMenu $False -UserFilterEnabled $False -Visible $True -WaitForPrinterCreation $False -WorkingDirectory $AppFolder)
				$AppNew = Get-BrokerApplication -AdminAddress $NewAdminAddress -Name $AppName -MaxRecordCount 999999
				$UidNew = $AppNew.uid
				foreach($User in $Users){
					[void](Add-BrokerUser -AdminAddress $NewAdminAddress $user -Application $uidNew)
				}
				[void](Set-BrokerApplication -AdminAddress $NewAdminAddress -InputObject $AppNew -UserFilterEnabled $FilterUsers)
		}


#To chage the icon of the apps, I am just using a variation of the below script after finding out the icon id of each app from the old farm.  I am doing this since we only have about 10 – 15 apps in total for each location. This will find the application and then set the iconUID to 8.  I am sure you can script this out too if you can figure out which old UID is used for the UID in the new farm.   

Get-brokerapplication -adminaddress $NewAdminAddress -clientfolder $clientfolder -publishedname [put the publishedname here] |set-brokerapplication -iconUID 8 

When I was migrating the first few location, it took me at least 30 minutes each.  With this script in place, this can be done in a minute.  (as long as all the names are consistent)  For 100 locations, that’s 3,000 minutes = 50 hours = 2.1 days saved!

*I prefer to migrate the apps by Machine Catalogs/Delivery Groups. The script can be modified to just copy everything from the old farm to the new farm. If you have a more complex structure that uses tags, application groups and Delivery Groups that has machines from multiple Machine Catalogs, you will need to modify the script further.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s