My client wanted to redirect the personal documents for the published applications in XenApp to the local OneDrive folder on the user’s devices, and if possible, to create a OneDrive folder shortcut in XenApp looking exactly as the local OneDrive client in the file explorer. Mission accomplished. Discover how I have done it in this document.
Preamble
Current implementation (pre-migration): Microsoft OneDrive has been implemented as the default personal documents storage for the users on the VDI machines and laptops. In the current Citrix XenApp 6.5 farm (published applications), a network drive “O” is mapped on the local client machine using the “OneDrive Mapper” script and is then mapped in the XenApp published application environment via a Citrix policy (file redirection, network drives). The users have been instructed to save their documents in the mapped network drive “O” when using a XenApp published application. The personal folders (Documents, Pictures, etc) in XenApp are not redirected to the network drive and are pointing to the Citrix UPM user profile, which can be confusing when using the published application’s file explorer.
The current OneDrive redirection for the XenApp 6.5 farm is a complex setup involving configuration on all clients (VDI, laptops) with the “OneDrive mapper” script. Several issues have been reported to the service desk regarding the reliability of the “OneDrive mapper” script on laptops, and the disappearance of the redirected network drive “O” in XenApp published applications.
For the new Citrix XenApp 7.15 farm deployment, the OneDrive redirection should be redesigned to remove all pre-requisites on the client side (zero client configuration) in a more reliable approach with less “moving parts” on both servers and clients, and with a more seamless integration in the published applications’ file explorer for a better user experience.
OneDrive folder redirection for XenApp design
The OneDrive folder redirection configuration will use the Citrix client drive mapping and redirection technology to get access to the client device local disk drive and locate the user’s OneDrive folder.
The file explorer hosted in Citrix XenApp for the published application will have folder redirection implemented to seamlessly redirect the user’s personal folders to the local OneDrive client folder located on the user’s device.
In addition, the root OneDrive client folder will be added to the File Explorer as a network shortcut and appearing as the native OneDrive client root folder, even if the client is not actually installed on the Citrix XenApp servers.
Citrix client drive mapping configuration
Microsoft group policies have been used to implement the Citrix XenApp user policies. Note that Citrix does not support mixing Windows settings and Citrix settings in the same group policy. Two policies have been created, one to configure the Citrix client drive mapping policy, and one to configure the OneDrive redirection with PowerShell.
In the first policy, the Citrix File Redirection settings are configured in:
User Configuration / Policies / Citrix Policies / File Redirection
User personal folders redirection configuration
The user personal folders will be redirected into the user’s OneDrive folder as follow:
The user personal folder redirection will be set only if the following conditions are met:
• client drive is reachable (exists) AND the user OneDrive folder is reachable (exists)
If the user OneDrive folder exists but the target folders for the redirection do not, they will be created. For example, if the directory “Videos” is missing in the user’s local OneDrive folder, the directory will be created before configuring the redirection on the XenApp server.
If the conditions are not met, the redirection will not be set to the user’s local OneDrive folder, but to the Citrix user profile (on the XenApp server).
The embedded folder redirection configuration in the Microsoft group policy could not be use in this complex scenario. A power shell login script has been defined on the XenApp server for the OneDrive folder redirection: (see Annex A).
OneDrive network shortcut in file explorer configuration
To achieve the best user experience with the OneDrive folder redirection, a network shortcut pointing to the root of the user’s local OneDrive folder on the user’s device will be added with the native OneDrive icon to the hosted file explorer in XenApp for the published applications.
The shortcut will be created as a symbolic link (directory junction) in the “Network Shortcuts” directory located in:
%username%\AppData\Roaming\Microsoft\Windows\Network Shortcuts
Note that the type of shortcut is a “File folder”, showing that this is not just a regular shortcut (.lnk) but a directory junction:
To get the OneDrive native icon for the shortcut and for this shortcut to appear in the save location window for legacy applications, it is required to copy a “desktop.ini” file at the root of the OneDrive folder. The “desktop.ini” file contains the property of the folder and the icon file location:
1 2 3 4 5 |
[.ShellClassInfo] CLSID2={0AFACED1-E828-11D1-9187-B532F1E9575D} Flags=2 IconFile=C:\Local\OneDrive.exe IconIndex=0 |
The Shell Class Info is pointing to the registry
1 |
“HKEY_CLASS_ROOT\CLSID\{0AFACED1-E828-11D1-9187-B532F1E9575D}” |
This is to help the embedded Explorer from published applications to see the directory junction as a directory. The icon file is located on the server itself. The network shortcut is created with a PowerShell script at the user login on the XenApp server.
The script will test the client local drive mapping and the OneDrive folder path on the client device before creating the shortcut. The script will then create the directory junction and create the “desktop.ini” at the root of the OneDrive folder. The script is available at this end of this document (see Annex B).
Annex A: XenApp_OneDrive_Redirection.PS1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# OneDrive redirection for XenApp Script # Olivier MARCHETTA - IT Contractor - London - 2018 # This script will redirect XenApp folders to the OneDrive folders on the connected client device # If the client device OneDrive folder is not available, the script will redirect the folders on the local XenApp server # The script will also create missing folders in OneDrive (fixing a previous old migration from Windows XP profiles) # 1. Init variables $remoteonedrive = "\\client\c$\users\" + $env:UserName + "\OneDrive - Company Name" $testremoteonedrive = Test-Path -Path $remoteonedrive $usershellfolders = Get-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" $restartexplorer = 0 # 2. Redirecting XenApp profile folders to the client OneDrive folders if available If ($testremoteonedrive -eq $true){ #Init OneDrive paths variables $mydesktoppath = Join-Path $remoteonedrive -ChildPath "Desktop" $mydocumentspath = Join-Path $remoteonedrive -ChildPath "Documents" $mypicturespath = Join-Path $remoteonedrive -ChildPath "Pictures" $mymusicpath = Join-Path $remoteonedrive -ChildPath "Music" $myvideospath = Join-Path $remoteonedrive -ChildPath "Videos" $mydownloadspath = Join-Path $remoteonedrive -ChildPath "Downloads" $mylinkspath = Join-Path $remoteonedrive -ChildPath "Links" #Init Test-Path variables $mydesktoptest = Test-Path -Path $mydesktoppath $mydocumentstest = Test-Path -Path $mydocumentspath $mypicturestest = Test-Path -Path $mypicturespath $mymusictest = Test-Path -Path $mymusicpath $myvideostest = Test-Path -Path $myvideospath $mydownloadstest = Test-Path -Path $mydownloadspath $mylinkstest = Test-Path -Path $mylinkspath #Testing OneDrive subfolders and creating missing directories If ($mydesktoptest -eq $false){ New-Item -Path $remoteonedrive -Name "Desktop" -ItemType "directory" -ErrorAction SilentlyContinue Sleep -Milliseconds 200} If ($mydocumentstest -eq $false){ New-Item -Path $remoteonedrive -Name "Documents" -ItemType "directory" -ErrorAction SilentlyContinue Sleep -Milliseconds 200} If ($mypicturestest -eq $false){ New-Item -Path $remoteonedrive -Name "Pictures" -ItemType "directory" -ErrorAction SilentlyContinue Sleep -Milliseconds 200} If ($mymusictest -eq $false){ New-Item -Path $remoteonedrive -Name "Videos" -ItemType "directory" -ErrorAction SilentlyContinue Sleep -Milliseconds 200} If ($myvideostest -eq $false){ New-Item -Path $remoteonedrive -Name "Music" -ItemType "directory" -ErrorAction SilentlyContinue Sleep -Milliseconds 200} If ($mydownloadstest -eq $false){ New-Item -Path $remoteonedrive -Name "Downloads" -ItemType "directory" -ErrorAction SilentlyContinue Sleep -Milliseconds 200} If ($mylinkstest -eq $false){ New-Item -Path $remoteonedrive -Name "Links" -ItemType "directory" -ErrorAction SilentlyContinue Sleep -Milliseconds 200} #Comparing and updating User Shell Folders in registry If ($mydesktoppath -ne $usershellfolders.Desktop){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "Desktop" -value $mydesktoppath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mydocumentspath -ne $usershellfolders.Personal){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "Personal" -value $mydocumentspath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mydocumentspath -ne $usershellfolders.'{F42EE2D3-909F-4907-8871-4C22FC0BF756}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{F42EE2D3-909F-4907-8871-4C22FC0BF756}" -value $mydocumentspath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mypicturespath -ne $usershellfolders."My Pictures"){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "My Pictures" -value $mypicturespath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mypicturespath -ne $usershellfolders.'{0DDD015D-B06C-45D5-8C4C-F59713854639}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{0DDD015D-B06C-45D5-8C4C-F59713854639}" -value $mypicturespath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mymusicpath -ne $usershellfolders."My Music"){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "My Music" -value $mymusicpath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mymusicpath -ne $usershellfolders.'{A0C69A99-21C8-4671-8703-7934162FCF1D}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{A0C69A99-21C8-4671-8703-7934162FCF1D}" -value $mymusicpath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($myvideospath -ne $usershellfolders."My Video"){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "My Video" -value $myvideospath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($myvideospath -ne $usershellfolders.'{35286A68-3C57-41A1-BBB1-0EAE73D76C95}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{35286A68-3C57-41A1-BBB1-0EAE73D76C95}" -value $myvideospath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mydownloadspath -ne $usershellfolders.'{374DE290-123F-4565-9164-39C4925E467B}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{374DE290-123F-4565-9164-39C4925E467B}" -value $mydownloadspath -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mylinkspath -ne $usershellfolders.Favorites){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "Favorites" -value $mylinkspath -ErrorAction SilentlyContinue $restartexplorer = 1} } # 3. Fallback procedure: Redirecting user folders to the local XenApp profile on the server if the remote OneDrive is not available # Note: we assume that the local user profile default folders exist and do not need to be tested or created If ($testremoteonedrive -eq $false){ #Init local profile paths variables $mydesktoppath = Join-Path $env:userprofile -ChildPath "Desktop" $mydocumentspath = Join-Path $env:userprofile -ChildPath "Documents" $mypicturespath = Join-Path $env:userprofile -ChildPath "Pictures" $mymusicpath = Join-Path $env:userprofile -ChildPath "Music" $myvideospath = Join-Path $env:userprofile -ChildPath "Videos" $mydownloadspath = Join-Path $env:userprofile -ChildPath "Downloads" $mylinkspath = Join-Path $env:userprofile -ChildPath "Links" #Comparing and updating User Shell Folders in registry If ($mydesktoppath -ne $usershellfolders.Desktop){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "Desktop" -value "%USERPROFILE%\Desktop" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mydocumentspath -ne $usershellfolders.Personal){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "Personal" -value "%USERPROFILE%\Documents" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mydocumentspath -ne $usershellfolders.'{F42EE2D3-909F-4907-8871-4C22FC0BF756}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{F42EE2D3-909F-4907-8871-4C22FC0BF756}" -value "%USERPROFILE%\Documents" -ErrorAction Stop $restartexplorer = 1} If ($mypicturespath -ne $usershellfolders."My Pictures"){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "My Pictures" -value "%USERPROFILE%\Pictures" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mypicturespath -ne $usershellfolders.'{0DDD015D-B06C-45D5-8C4C-F59713854639}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{0DDD015D-B06C-45D5-8C4C-F59713854639}" -value "%USERPROFILE%\Pictures" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mymusicpath -ne $usershellfolders."My Music"){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "My Music" -value "%USERPROFILE%\Music" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mymusicpath -ne $usershellfolders.'{A0C69A99-21C8-4671-8703-7934162FCF1D}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{A0C69A99-21C8-4671-8703-7934162FCF1D}" -value "%USERPROFILE%\Music" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($myvideospath -ne $usershellfolders."My Video"){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "My Video" -value "%USERPROFILE%\Videos" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($myvideospath -ne $usershellfolders.'{35286A68-3C57-41A1-BBB1-0EAE73D76C95}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{35286A68-3C57-41A1-BBB1-0EAE73D76C95}" -value "%USERPROFILE%\Videos" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mydownloadspath -ne $usershellfolders.'{374DE290-123F-4565-9164-39C4925E467B}'){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "{374DE290-123F-4565-9164-39C4925E467B}" -value "%USERPROFILE%\Downloads" -ErrorAction SilentlyContinue $restartexplorer = 1} If ($mylinkspath -ne $usershellfolders.Favorites){ Set-ItemProperty "hkcu:\software\microsoft\windows\currentversion\explorer\User Shell Folders" -Name "Favorites" -value "%USERPROFILE%\Favorites" -ErrorAction SilentlyContinue $restartexplorer = 1} } #Restart explorer If ($restartexplorer = 1){ $proc = @(Get-CimInstance -Query "Select * from Win32_Process where name = 'explorer.exe'") for ($i=0; $i -lt $proc.length; $i++){ $owner = ($proc[$i] | Invoke-CimMethod -MethodName GetOwner).User if ($owner -eq $env:UserName){ $processid = ($proc[$i]).ProcessId Stop-Process -Id $processid}} |
Annex B: XenApp_OneDrive_Shortcut.PS1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# OneDrive Network Place in File Explorer # Olivier MARCHETTA - IT Contractor - London - 2018 function Add-NetworkLocation <# Author: Tom White, 2015. Description: Creates a network location shortcut using the specified path, name and target. Replicates the behaviour of the 'Add Network Location' wizard, creating a special folder as opposed to a simple shortcut. Returns $true on success and $false on failure. Use -Verbose for extended output. Example: Add-NetworkLocation -networkLocationPath "$env:APPDATA\Microsoft\Windows\Network Shortcuts" -networkLocationName "Network Location" -networkLocationTarget "\\server\share" -Verbose #> { [CmdLetBinding()] param ( [Parameter(Mandatory=$true)][string]$networkLocationPath, [Parameter(Mandatory=$true)][string]$networkLocationName , [Parameter(Mandatory=$true)][string]$networkLocationTarget ) Begin { Write-Verbose -Message "Network location path: `"$networkLocationPath`"." Write-Verbose -Message "Network location name: `"$networkLocationName`"." Write-Verbose -Message "Network location target: `"$networkLocationTarget`"." Set-Variable -Name desktopIniContent -Option ReadOnly -value ([string]"[.ShellClassInfo]`r`nCLSID2={0AFACED1-E828-11D1-9187-B532F1E9575D}`r`nFlags=2`r`nIconFile=C:\Local\OneDrive.exe`r`nIconIndex=0") } Process { Write-Verbose -Message "Checking that `"$networkLocationPath`" is a valid directory..." if(Test-Path -Path $networkLocationPath -PathType Container) { try { Write-Verbose -Message "Creating `"$networkLocationPath\$networkLocationName`"." [void]$(New-Item -Path "$networkLocationPath\$networkLocationName" -ItemType Directory -ErrorAction Stop) Write-Verbose -Message "Setting system attribute on `"$networkLocationPath\$networkLocationName`"." Set-ItemProperty -Path "$networkLocationPath\$networkLocationName" -Name Attributes -Value ([System.IO.FileAttributes]::System) -ErrorAction Stop } catch [Exception] { Write-Error -Message "Cannot create or set attributes on `"$networkLocationPath\$networkLocationName`". Check your access and/or permissions." return $false } } else { Write-Error -Message "`"$networkLocationPath`" is not a valid directory path." return $false } try { Write-Verbose -Message "Creating `"$networkLocationPath\$networkLocationName\desktop.ini`"." [object]$desktopIni = New-Item -Path "$networkLocationPath\$networkLocationName\desktop.ini" -ItemType File Write-Verbose -Message "Writing to `"$($desktopIni.FullName)`"." Add-Content -Path $desktopIni.FullName -Value $desktopIniContent } catch [Exception] { Write-Error -Message "Error while creating or writing to `"$networkLocationPath\$networkLocationName\desktop.ini`". Check your access and/or permissions." return $false } try { $WshShell = New-Object -ComObject WScript.Shell Write-Verbose -Message "Creating shortcut to `"$networkLocationTarget`" at `"$networkLocationPath\$networkLocationName\target.lnk`"." $Shortcut = $WshShell.CreateShortcut("$networkLocationPath\$networkLocationName\target.lnk") $Shortcut.TargetPath = $networkLocationTarget $Shortcut.Description = "Created $(Get-Date -Format s) by $($MyInvocation.MyCommand)." $Shortcut.Save() } catch [Exception] { Write-Error -Message "Error while creating shortcut @ `"$networkLocationPath\$networkLocationName\target.lnk`". Check your access and permissions." return $false } return $true } } function Remove-Symlink { param ( [Parameter(Position=0, Mandatory=$true)] [string] $Link ) [System.IO.Directory]::Delete($Link, $true) } $remoteonedrive = "\\client\c$\users\" + $env:UserName + "\OneDrive - Company Name" $testremoteonedrive = Test-Path -Path $remoteonedrive $testsymlink = Test-Path -Path "$env:APPDATA\Microsoft\Windows\Network Shortcuts\OneDrive - Company Name" $testdesktopini = Test-Path -Path "$env:APPDATA\Microsoft\Windows\Network Shortcuts\OneDrive - Company Name\desktop.ini" if ($testsymlink -eq $true){ Remove-Symlink -Link "$env:APPDATA\Microsoft\Windows\Network Shortcuts\OneDrive - Company Name"} if ($testremoteonedrive -eq $true){ Remove-Item -Path "$remoteonedrive\desktop.ini" -Force Add-NetworkLocation -networkLocationPath "$env:APPDATA\Microsoft\Windows\Network Shortcuts" -networkLocationName "OneDrive - Company Name" -networkLocationTarget $remoteonedrive -Verbose Copy-Item -Path "$env:APPDATA\Microsoft\Windows\Network Shortcuts\OneDrive - Company Name\desktop.ini" -Destination $remoteonedrive\desktop.ini -Force} |