NOTE: This blog was also posted on MyCUGC.org (link) on March 3rd, 2021.
About three years ago, I posted a blog about how to perform an unattended installation and configuration of Citrix StoreFront with Ivanti Automation. It did the installation, the creation of a new store and it added a second StoreFront server to the server group.
Since most organizations are stepping away from (still awesome) tools like Ivanti Automation and are moving towards solutions like Puppet, Ansible, Chocolatey, Packer, plain PowerShell, etc., I wanted to create a PowerShell script that does the same, but does not require an automation tool.
In this short blog, I will post how it works, how you can use it and of course the scripts themselves. Keep in mind that this is just an example, it is not a definitive solution for deploying StoreFront. Also, these scripts are not signed with a certificate because you need to enter your environment specific variables.
These scripts have been tested with Citrix StoreFront 1912 Cumulative Update 2 on Windows Server 2019 (Build 17763.1697). I have divided it into three PowerShell scripts (contents and download link further down the page) which perform the following actions:
SFScript-1-Install-StoreFront.ps1
* Should be run on all StoreFront servers.
– Add required Windows Features (with reboot if needed)
– Install StoreFront (with reboot if needed)
SFScript-2-Setup-Primary-StoreFront-Instance.ps1
* Should be run on the first StoreFront server.
– Create store
– Create Receiver for Web Site
– Create (self-signed) certificate
– Bind certificate to site
– Create redirect to Receiver for Web Site
– Configure dedicated port for propagating (optional)
SFScript-3-Join-Existing-StoreFront-Instance.ps1
* Should be run on all subsequent StoreFront servers.
– Get passcode from primary StoreFront instance
– Join StoreFront server group with passcode
– Propagate changes from primary StoreFront instance
– Create (self-signed) certificate
– Bind certificate to site
– Copy redirect from primary StoreFront instance
To use them, please follow these steps:
1. Change the variable in script 1 that has the ‘EDIT’-comment behind them to a value that matches your environment.
2. Run script 1 on all (planned) StoreFront servers.
If a reboot is needed, you can run the same script after the reboot and it will simply skip the parts it has already done.
3. Change all variables in script 2 that have the ‘EDIT’-comment behind them to values that match your environment.
4. Run script 2 on the primary StoreFront server.
5. Change the variable in script 3 that has the ‘EDIT’-comment behind them.
This simply is the hostname, IP address or FQDN of the primary StoreFront server.
6. Run script 3 on all subsequent StoreFront servers.
Run it on one server at a time and not all at once.
Good to know: You can define a specific port for propagating changes between the StoreFront instances. This is useful if these are in different subnets and the connection goes through a firewall. Otherwise you can leave it empty. It will then use a random port.
Also beware that these scripts will create a self-signed SSL certificate. It is highly recommended to replace this with a proper SSL certificate.
Script 1 – Install StoreFront
# SCRIPT INFO ------------------- # Script that installs Citrix StoreFront # Run on designated StoreFront server # By Chris Jeucken # ------------------------------- # INSTALLATION VARIABLES -------- $SoftwarePath = "\\server\share\StoreFront 1912CU2" # <-- EDIT - Specify the folder where the CitrixStoreFront-x64.exe file is located (can also be a local folder) $StoreFrontInstaller = "CitrixStoreFront-x64.exe" $StoreFrontInstallerPath = $SoftwarePath + "\" + $StoreFrontInstaller # ------------------------------- # PREREQUISITES ----------------- # Set Stop on error $ErrorActionPreference = "Stop" # Disable open file security warnings $env:SEE_MASK_NOZONECHECKS = 1 # ------------------------------- # INSTALL FEATURES -------------- Import-Module ServerManager $FeatureInstall = Install-WindowsFeature -Name Web-Default-Doc,Web-Http-Errors,Web-Static-Content,Web-Http-Redirect,Web-Http-Logging,Web-Filtering,Web-Basic-Auth,Web-Windows-Auth,Web-Net-Ext45,Web-AppInit,Web-Asp-Net45,Web-ISAPI-Ext,Web-ISAPI-Filter,Web-Mgmt-Console,Web-Scripting-Tools,NET-Framework-45-ASPNET -Restart:$false if ($FeatureInstall.ExitCode -eq "NoChangeNeeded") { Write-Host "Required IIS Windows features already installed" -ForegroundColor Green } if ($FeatureInstall.ExitCode -eq "Success") { Write-Host "Reboot needed for IIS Windows features installation." -ForegroundColor Magenta Start-Sleep -Seconds 20 Restart-Computer -Force } # ------------------------------- # SCRIPT ------------------------ # Check if StoreFront is already installed if ((Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -like "Citrix*"}).Name -contains "Citrix StoreFront") { Write-Host "Citrix StoreFront already installed." -ForegroundColor Green } else { # Install StoreFront Write-Host "Installing Citrix StoreFront." -ForegroundColor Green $StoreFrontInstallerArguments = "-silent" Start-Process -FilePath $StoreFrontInstallerPath -ArgumentList $StoreFrontInstallerArguments -Wait -PassThru if ((Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -like "Citrix*"}).Name -contains "Citrix StoreFront") { Write-Host "Citrix StoreFront installed. Rebooting server." -ForegroundColor Magenta Start-Sleep -Seconds 20 Restart-Computer -Force } else { Write-Host "Citrix StoreFront installation failed." -ForegroundColor Red } } # -------------------------------
Script 2 – Setup primary StoreFront instance
# SCRIPT INFO ------------------- # Script that sets up the first StoreFront instance # Run on designated (primary) StoreFront server # By Chris Jeucken # ------------------------------- # CONFIGURATION VARIABLES ------- # User defined variables $SFBaseURL = "" # <-- EDIT - Define Base URL (e.g. https://storefront.local.lan) $XDDeliveryController1 = "" # <-- EDIT - Define primary Citrix VADs Delivery Controller (e.g. xddc1.local.lan) $XDDeliveryController2 = "" # <-- EDIT - Define secondary Citrix VADs Delivery Controller (leave empty if there is only one Delivery Controller) $StoreFriendlyName = "" # <-- EDIT - Define StoreFront store name $XDFarmName = "" # <-- EDIT - Define Citrix VADs farm name $SFCustomP2PPort = "" # <-- EDIT - OPTIONAL - Define port for propagating changes (leave empty for random port (default)) # Pre-defined variables $SiteID = 1 $StoreFriendlyNameWithoutSpaces = $StoreFriendlyName -replace '\s','' $StoreFriendlyNameWithoutSpacesWeb = $StoreFriendlyNameWithoutSpaces + "Web" $StoreVirtualPath = "/Citrix/" + $StoreFriendlyName -replace '\s','' $StoreVirtualPathWeb = $StoreVirtualPath + "Web" $StoreURL = $SFBaseURL + "/Citrix/" + $StoreFriendlyNameWithoutSpaces $FarmType = "XenDesktop" $TransportType = "HTTP" $ServicePort = "80" $SslRelayPort = "443" $RedirectFile = "SFRedirect.html" $RedirectPath = "C:\inetpub\wwwroot\" $RedirectPage = $RedirectPath + $Redirectfile $SFConfigFiles = "$env:ProgramFiles\Citrix\Receiver StoreFront\Services\SubscriptionsStoreService\Citrix.DeliveryServices.SubscriptionsStore.ServiceHost.exe.config", "$env:ProgramFiles\Citrix\Receiver StoreFront\Services\CredentialWallet\Citrix.DeliveryServices.CredentialWallet.ServiceHost.exe.config" # ------------------------------- # PREREQUISITES ----------------- # Set Stop on error $ErrorActionPreference = "Stop" # Import StoreFront PS modules (not needed if using PoSH 3.0+, but just to be sure) Import-Module Citrix.StoreFront . "C:\Program Files\Citrix\Receiver StoreFront\Scripts\ImportModules.ps1" Start-Sleep -Seconds 10 # ------------------------------- # SCRIPT ------------------------ # Create initial store (will be deleted after creation of the definitive store) Set-DSInitialConfiguration -HostBaseUrl $SFBaseURL ` -FarmName $XDFarmName ` -Port 80 ` -Transporttype HTTP ` -SslRelayPort 443 ` -Servers @("tempddc.domain.lan") ` -LoadBalance $false ` -FarmType "XenDesktop" ` -StoreVirtualPath /Citrix/TEMP ` -WebReceiverVirtualPath /Citrix/TEMPWeb # Create StoreFront store with one defined XenDesktop Delivery Controller if ($XDDeliveryController2 -eq "") { $AuthSummary = Get-DSAuthenticationServicesSummary -SiteID $SiteID Install-DSStoreServiceAndConfigure -SiteID $SiteID ` -FriendlyName $StoreFriendlyName ` -VirtualPath $StoreVirtualPath ` -AuthSummary $AuthSummary ` -FarmName $XDFarmName ` -FarmType $FarmType ` -Servers @($XDDeliveryController1) ` -TransportType $TransportType ` -ServicePort $ServicePort ` -SslRelayPort $SslRelayPort Install-DSWebReceiver -FriendlyName $StoreFriendlyName ` -SiteID 1 ` -StoreURL $StoreURL ` -useHttps $true ` -VirtualPath $StoreVirtualPathWeb } # Create StoreFront store with two defined XenDesktop Delivery Controllers if ($XDDeliveryController2 -ne "") { $AuthSummary = Get-DSAuthenticationServicesSummary -SiteID $SiteID Install-DSStoreServiceAndConfigure -SiteID $SiteID ` -FriendlyName $StoreFriendlyName ` -VirtualPath $StoreVirtualPath ` -AuthSummary $AuthSummary ` -FarmName $XDFarmName ` -FarmType $FarmType ` -Servers @($XDDeliveryController1,$XDDeliveryController2) ` -TransportType $TransportType ` -ServicePort $ServicePort ` -SslRelayPort $SslRelayPort Install-DSWebReceiver -FriendlyName $StoreFriendlyName ` -SiteID 1 ` -StoreURL $StoreURL ` -useHttps $false ` -VirtualPath $StoreVirtualPathWeb } # Create self signed certificate and bind it to site if ($SFBaseURL -like "*https*") { # Remove https from URL $SFBaseURLShort = ($SFBaseURL -replace "https://","") # Create self signed certificate $Certificate = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname $SFBaseURLShort # Add certificate to Trusted Root Certification Authorities $CertificateRootStore = New-Object System.Security.Cryptography.X509Certificates.X509Store -ArgumentList Root, LocalMachine $CertificateRootStore.Open("MaxAllowed") $CertificateRootStore.Add($Certificate) $CertificateRootStore.Close() # Get certificate thumbprint $Thumbprint = $Certificate.Thumbprint # Create new IIS binding New-WebBinding -Name "Default Web Site" -IP "*" -Port 443 -Protocol https -Verbose # Add certiticate to IIS binding Start-Sleep -Seconds 5 $Binding = Get-WebBinding -IPAddress "*" -Port 443 -Protocol https $Binding.AddSslCertificate($Thumbprint, "my") } # Create redirect to StoreFront site Add-Content -Path $RedirectPage -Value "" Add-WebConfiguration "system.webserver/defaultdocument/files" -atIndex 0 -Value $RedirectFile # Insert custom P2P port for propagating changes (otherwise it's random) if ($SFCustomP2PPort) { foreach ($SFConfigFile in $SFConfigFiles) { if (Test-Path -Path $SFConfigFile -ErrorAction SilentlyContinue) { $SFConfigFileContent = Get-Content -Path $SFConfigFile $SFConfigFileContent | ForEach-Object { if ($_ -like "*net.p2p://*") { $_ -replace "`">",":$SFCustomP2PPort`">" } else { $_ } } | Set-Content -Path $SFConfigFile -Force } } Restart-Service -Name CitrixSubscriptionsStore -Force Restart-Service -Name CitrixCredentialWallet -Force } # Remove initial store Remove-DSStore2 -SiteID 1 -VirtualPath "/Citrix/TEMP" # -------------------------------
Script 3 – Join existing StoreFront instance
# SCRIPT INFO ------------------- # Script that adds a server to an existing StoreFront instance # Run on designated (secondary) StoreFront server # By Chris Jeucken # ------------------------------- # CONFIGURATION VARIABLES ------- # User defined variables $SFExistingServer = "" # <-- EDIT - Define hostname or FQDN of primary StoreFront server # Pre-defined variables # Locations $RemoteConfigFolder = "_CONFIG" $RemoteConfigPath = "\\$SFExistingServer\c$" $RemoteRedirectPath = "\inetpub\wwwroot\" $LocalConfigFolder = "_CONFIG" $LocalConfigPath = "C:" $LocalRedirectPath = "C:\inetpub\wwwroot\" # Files and scripts $RedirectFile = "SFRedirect.html" $RemoteRedirectPage = $RemoteConfigPath + $RemoteRedirectPath + $Redirectfile $RemotePasscodeScript = "$RemoteConfigPath\$RemoteConfigFolder\SFPasscodeScript.ps1" $LocalPasscodeScript = "$LocalConfigPath\$LocalConfigFolder\SFPasscodeScript.ps1" # ------------------------------- # PREREQUISITES ----------------- # Set Stop on error $ErrorActionPreference = "Stop" # Import StoreFront PS modules (not needed if using PoSH 3.0+, but just to be sure) Import-Module Citrix.StoreFront . "C:\Program Files\Citrix\Receiver StoreFront\Scripts\ImportModules.ps1" Start-Sleep -Seconds 10 # ------------------------------- # SCRIPT ------------------------ # Create PowerShell Session object for primary StoreFront server $PSSession = New-PSSession -ComputerName $SFExistingServer # Check if folders and/or files already exist if ((Test-Path $RemoteConfigPath\$RemoteConfigFolder) -eq 0 ) { mkdir $RemoteConfigPath\$RemoteConfigFolder } if ((Test-Path $RemotePasscodeScript) -ne 0) { Remove-Item $RemotePasscodeScript } if ((Test-Path $RemoteConfigPath\$RemoteConfigFolder\Passcode.txt) -ne 0) { Remove-Item $RemoteConfigPath\$RemoteConfigFolder\Passcode.txt } # Create script on existing StoreFront server Add-Content -Path $RemotePasscodeScript -Value ". ""C:\Program Files\Citrix\Receiver StoreFront\Scripts\ImportModules.ps1""" Add-Content -Path $RemotePasscodeScript -Value "Start-DSClusterJoinService" Add-Content -Path $RemotePasscodeScript -Value "`$Passcode = (Get-DSClusterJoinServicePasscode).Response.Passcode" Add-Content -Path $RemotePasscodeScript -Value "`$Passcode > $LocalConfigPath\$LocalConfigFolder\Passcode.txt" # Run script on existing StoreFront server schtasks /Create /F /TN SFPasscodeScript /S $SFExistingServer /RU "SYSTEM" /TR "powershell.exe -File $LocalPasscodeScript" /SC once /ST 23:30 schtasks /Run /TN SFPasscodeScript /S $SFExistingServer While ((schtasks /Query /TN SFPasscodeScript /S $SFExistingServer /fo List)[5] -notlike "*Ready") { Write-Verbose -Message "Waiting on scheduled task..." } Start-Sleep -Seconds 5 $SFPasscode = Get-Content -Path "$RemoteConfigPath\$RemoteConfigFolder\Passcode.txt" schtasks /Delete /TN SFPasscodeScript /S $SFExistingServer /F Remove-Item -Path $RemoteConfigPath\$RemoteConfigFolder\Passcode.txt Remove-Item -Path $RemotePasscodeScript # Join new server to StoreFront group and wait a while for completion Start-DSClusterJoinService Start-DSClusterMemberJoin -authorizerHostName $SFExistingServer -authorizerPasscode $SFPasscode Start-Sleep -s 300 Invoke-Command -Session $PSSession -Scriptblock { . "$env:ProgramFiles\Citrix\Receiver StoreFront\Scripts\ImportModules.ps1" Stop-DSClusterJoinService } Start-Sleep -seconds 30 Stop-DSClusterJoinService # Propagate changes from existing StoreFront server $PSSession = New-PSSession -computerName $SFExistingServer Invoke-Command -Session $PSSession -Scriptblock { Add-PSSnapin Citrix* Start-DSConfigurationReplicationClusterUpdate -confirm:$FALSE } do { Write-Host "Waiting for StoreFront replication..." -ForegroundColor Magenta Start-Sleep -Seconds 60 } while ((Get-WmiObject -Class Win32_PerfFormattedData_PerfProc_Process -Property Name,PercentProcessorTime -Filter "Name LIKE 'Citrix.DeliveryServices.ConfigurationReplicationService%'").PercentProcessorTime -ne "0") # Create self signed certificate and bind it to site $SFBaseUrlFull = Get-DSHostBaseUrl $SFBaseUrl = $SFBaseURLFull.hostBaseUrl if ($SFBaseURL -like "*https*") { # Change URL to correct format $SFBaseURLShort = ($SFBaseURL -replace "https://","") $SFBaseURLShort = ($SFBaseURLShort -replace "/""") # Create self signed certificate $Certificate = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname $SFBaseURLShort # Add certificate to Trusted Root Certification Authorities $CertificateRootStore = New-Object System.Security.Cryptography.X509Certificates.X509Store -ArgumentList Root, LocalMachine $CertificateRootStore.Open("MaxAllowed") $CertificateRootStore.Add($Certificate) $CertificateRootStore.Close() # Get certificate thumbprint $Thumbprint = $Certificate.Thumbprint # Create new IIS binding New-WebBinding -Name "Default Web Site" -IP "*" -Port 443 -Protocol https # Add certiticate to IIS binding $Binding = Get-WebBinding -IPAddress "*" -Port 443 -Protocol https $Binding.AddSslCertificate($Thumbprint, "my") } # Create redirect to StoreFront site Copy-Item -Path $RemoteRedirectPage -Destination $LocalRedirectPath Add-WebConfiguration "system.webserver/defaultdocument/files" -atIndex 0 -Value $RedirectFile # -------------------------------
DISCLAIMER: Once again: I’m in no way an expert PowerShell scripter, so it might not be the most efficient code, but it gets the job done. And, of course, feel free to use it/alter it/publish it as your own.