Feb 152018
 

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:

[.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

“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

# 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

# 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}

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

This site uses Akismet to reduce spam. Learn how your comment data is processed.