Desktop and Application Streaming

Launching local applications from an Amazon AppStream 2.0 streaming session

With Amazon AppStream 2.0, applications run securely in the cloud, isolated from end users’ local machines. In some scenarios, customers require interaction between remote and local applications. For example, consider a hosted application that exports data to an external program, such as a spreadsheet or PDF application that is installed and licensed on a local machine. Likewise, a local application may output a file that needs to be opened automatically in the appropriate AppStream 2.0 hosted application.

With the file system redirection feature, administrators can configure a folder to be redirected from the local client into the remote AppStream 2.0 session. With this configuration in place, you can exchange files with the AppStream 2.0 session using native Windows Explorer folder interactions. You can then utilize a FileSystemWatcher to monitor that folder for activity, and automatically initiate secondary actions, such as opening the file in a specific program.

This article includes a sample script that configures a freshly installed AppStream 2.0 Windows client to redirect a local folder into the remote session. An example PowerShell script that enables a file system watcher to automatically react to changes within that folder is also provided.

The scenario provided in this blog demonstrates how a remote AppStream 2.0 hosted application can automatically launch content on a local machine. You can utilize this example as a template and make any necessary modifications to meet your specific use case. For instance, the application interaction can be reversed by configuring the file system watcher inside your AppStream 2.0 image. Additionally, the sample script provides an outline for reacting to additional file actions such as changes, renames, and deletes.

Time to read 10 minutes
Time to complete 1 hour
Cost to complete (estimated) $0
Learning level Advanced (300)
Services used Amazon AppStream 2.0

Walkthrough

In this guide, you will complete the following tasks:

  1. Configure the AppStream 2.0 Windows client folder redirection feature.
  2. Launch a script to monitor for new files within the redirected folder.
  3. Test the interaction between the remote and local application.
  4. Learn how to expand upon the demonstrated functionality.

Prerequisites

This article assumes that you have the following:

Step 1: Configure AppStream 2.0 Windows client

In this step, you will install the AppStream 2.0 Windows client and configure the folder redirection feature. A sample PowerShell script to programmatically turn on folder redirection without user input is attached. This script assumes that the user has not configured any prior folder redirects in the client.

  1. Download and install the latest AppStream 2.0 client onto a test workstation.
  2. Create a local directory that will be made visible to the remote AppStream 2.0 hosted application. For this guide, I am using C:\AS2_Interaction.
  3. Copy the contents of the following PowerShell script and paste into the text editor of your choice.
  4. If you are using a folder other than C:\AS2_Interaction, modify the path stored in the variable $MonitorFolder in the script.
  5. Save the script, AS2-Client-FolderRedirect.ps1, to your test workstation.
  6. Open the context (right-click) menu on the script, and then choose Run with PowerShell to configure the AppStream 2.0 client folder redirection feature.
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


# Local folder to be made available in remote session, this is the local folder that will be monitored for file activity
$MonitorFolder = "c:\AS2_Interaction"

# Registry location for AppStream 2.0 Windows client configuration
$registryPath = "HKCU:\Software\Amazon\AppStream Client"

# Location in user profile where AppStream client user preferences file exists
$preferenceFolder = "${env:LOCALAPPDATA}/Amazon/AppStreamClient/preferences"
$preferenceFilePath = "$preferenceFolder/user_preferences.json"

# Set regitry key to configure default AppStream 2.0 client with file redirection folder
New-ItemProperty -Path $registryPath -Name "FileRedirectionCustomDefaultFolders" -Value $MonitorFolder -PropertyType String -Force | Out-Null

 
# Create a user preferences file for the AppStream 2.0 client which will configure the drive mapping 
# Object formatted for AppStream client user preferences file
$sharedFolders = [PSCustomObject]@{
    AS2_File_Redirect = [PSCustomObject]@{folderPath = $MonitorFolder; redirectedDriveType = 0; label = "AS2_Folder_Redirect"; redirectedDriveStatus = 1}
}

if ((Test-Path $preferenceFilePath) -and ($Null -ne (Get-Content $preferenceFilePath))) {
    # If existing preferences file exists, import it
    $prefJson = Get-Content $preferenceFilePath -raw | ConvertFrom-Json
}
else {
    # If no existing preferences file exists, start with blank file
    $prefJson = [ordered]@{}
}

# Convert object to JSON
$defaultFolderStr = $sharedFolders | ConvertTo-Json -Compress
 
# Add redirected folder to AppStream 2.0 client configuration
$prefJson | Add-Member -Type NoteProperty -Name 'FileRedirectionFolders' -Value $defaultFolderStr -Force

# Write out the updated user preferences file to their profile
$prefJson | ConvertTo-Json | set-content $preferenceFilePath 

Step 2: Run the file system monitor

To test the application interaction, manually run the file system monitor script. Once you have successfully tested the process, you should configure the file system monitor script to run automatically for your users. This can be accomplished using methods such as Windows Scheduled Tasks, logon scripts, or AppStream 2.0 session scripts. The sample script is intended to be used for testing the solution. You will need to modify it and fully test in your environment for your specific use case.

  1. Copy the contents of the PowerShell script and paste it into the text editor of your choice.
  2. If you are using a folder other than the default, C:\AS2_Interaction, modify the path stored in the variable $MonitorFolder in the script.
  3. Save the script, AS2-FileSystemMonitor.ps1, to your test workstation.
  4. Open the context (right-click) menu on the script, and then choose Run with PowerShell.
  5. You should see a PowerShell window appear, indicating that it is watching for changes in C:\AS2_Interaction. Leave this box open while testing the solution.
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


# Which file actions to monitor
$MonitorCreate = $true
$MonitorDelete = $false
$MonitorChange = $false
$MonitorRename = $false

# What types of files to monitor
$MonitorFilter = "*.txt"

# The folder to monitor for file activity
$MonitorFolder = "c:\AS2_Interaction\"

# Monitor subfolders of the monitored folder
$MonitorSubFolders = $false

# What program to call
$CreatedProcess = "c:\windows\notepad.exe"
#$DeletedProcess =
#$RenamedProcess = 
#$ChangedProcess =

# Which file properties you want to monitor
$AttributeFilter = [IO.NotifyFilters]::FileName, [IO.NotifyFilters]::LastWrite 

# Location to write log file to
$EnableLog = $true
$LogFile = "c:\AS2_Interaction\as2-interaction-debug-log-events.log"

#Variable to prevent duplicate event triggers
$global:canDoEvent = $True #To prevent multiple triggers for one file action

try {
  # Create file system watcher using folder and filter specified  
  $watcher = New-Object -TypeName System.IO.FileSystemWatcher -Property @{
    Path                  = $MonitorFolder
    Filter                = $MonitorFilter
    IncludeSubdirectories = $MonitorSubFolders
    NotifyFilter          = $AttributeFilter
  }

  # Code which will be executed every time a registered file action is detected
  $changeAction = {
    if ($global:canDoEvent) { #To prevent multiple triggers for one file action

        Start-Sleep -Seconds 1 

        $global:canDoEvent = $False #NEW  #To prevent multiple triggers for one file action
    
        # change type information:
        $path = $event.SourceEventArgs.FullPath
        $changeType = $event.SourceEventArgs.ChangeType      
       
        # Define what to do based on file action type
        switch ($changeType) {
          'Changed' { 
            # Actions to take when file is changed

            # Insert actions here

            if ($EnableLog) { 
                Add-Content $LogFile -value "$(Get-Date) - $changeType, $path" 
                Write-Host "$(Get-Date) - $changeType, $path" 
            }        
          }
          'Created' {
            # Actions to take when file is created
        
            # Example of using a specific program to handle opening the file
            start-process ("""$CreatedProcess""") -ArgumentList ("""$path""")
        
            # Example of using default file type association to handle opening the file
            #start-process $path

            if ($EnableLog) { 
                Add-Content $LogFile -value "$(Get-Date) - $changeType, $path" 
                Write-Host "$(Get-Date) - $changeType, $path" 
            }
          }
          'Deleted' { 
            # Actions to take when file is deleted

            # Insert actions here

            if ($EnableLog) { 
                Add-Content $LogFile -value "$(Get-Date) - $changeType, $path" 
                Write-Host "$(Get-Date) - $changeType, $path" 
            }  
          }
          'Renamed' { 
            # Actions to take when file is renamed

            # Insert actions here

            if ($EnableLog) { 
                Add-Content $LogFile -value "$(Get-Date) - $changeType, $path" 
                Write-Host "$(Get-Date) - $changeType, $path" 
            }     
          }      
          default { 
            # Actions to take on any other or unknown trigger
            if ($EnableLog) { Add-Content $LogFile -value "Unknown file action, $changeType" } 
          }
        }
    }
  }

  # Register monitors based on what action types required
  $handlers = . {
    if ($MonitorChange) { Register-ObjectEvent -InputObject $watcher -EventName Changed  -Action $changeAction }
    if ($MonitorCreate) { Register-ObjectEvent -InputObject $watcher -EventName Created  -Action $changeAction }
    if ($MonitorDelete) { Register-ObjectEvent -InputObject $watcher -EventName Deleted  -Action $changeAction }
    if ($MonitorRename) { Register-ObjectEvent -InputObject $watcher -EventName Renamed  -Action $changeAction }
  }

  # Start monitoring
  $watcher.EnableRaisingEvents = $true
  if ($EnableLog) { 
    Add-Content $LogFile -value "$(Get-Date), Begin watching for changes to $MonitorFolder with filter $MonitorFilter"
    Write-Host  "Begin watching for changes to $MonitorFolder with filter $MonitorFilter"
   }

  # Loop to keep script active
  do {
    $global:canDoEvent = $True #To prevent multiple triggers for one file action
    Start-Sleep -Seconds 2  #Delay to prevent multiple triggers for one file action   
  } while ($true)
}
finally {
  # Executed when user presses CTRL+C
  
  # Stop monitoring
  $watcher.EnableRaisingEvents = $false
  
  # Remove the event handlers
  $handlers | ForEach-Object {
    Unregister-Event -SourceIdentifier $_.Name
  }
  
  # Remove background jobs
  $handlers | Remove-Job
  
  # Remove filesystemwatcher
  $watcher.Dispose()
  
  #Write-Warning "Event Handler disabled, monitoring ends."
  if ($EnableLog) { Add-Content $LogFile -value "$(Get-Date), End watching for changes to $MonitorFolder" }
} 

Step 3: Create sample AppStream 2.0 fleet and stack

Now that the AppStream 2.0 client is configured to redirect a local folder into the session, you will create a fleet and stack using a service provided sample image. This sample image contains the Notepad++ application, which is used as a baseline example in this article’s instructions. Alternatively, if you have your own fleet and stack that contains a text editor to generate .txt files, that can be used.

  1. Follow the Amazon AppStream 2.0 Getting Started procedure in the administration guide. Once complete, you will have a stack running and a Streaming URL in your clipboard.
  2. While following the quick start wizard, ensure you leave File transfer set to Upload and download.

Step 4: Test local and remote application interaction

In this step, you will demonstrate the interaction between a remote application running in AppStream 2.0 and a local application.

  1. Launch the AppStream 2.0 client installed in step 1.
  2. Paste the Stream URL copied during the Getting Started guide in Step 3 into the URL box in the client.
  3. Choose Connect.
  4. Choose the icon for Notepad++.
  5. Type some text into the application.
  6. Choose File, Save As.
  7. In the Save As dialog box, browse to the local shared folder, c_AS2_Interaction.
  8. For File name, enter remotefile.txt.
  9. Choose Save.
  10. Within a few seconds you should see Notepad open locally and contain the same text you entered into the remote Notepad++ application.

Next Steps

You have now demonstrated the process for remote and local applications to interact via a file system redirection. In this article you configured a local application to automatically launch when a remote file is saved. To configure the reverse workflow, you would run the file system watcher script inside the AppStream 2.0 session instead of locally. Then, when a file is saved locally to the C:\AS2_Interaction folder, it would be detected in the remote AppStream 2.0 session.

The sample script provided with this article has placeholder sections for reacting to additional types of file actions, such as file changes, renames, and deletes. Modify the value of the $MonitorDelete, $MonitorChange, or $MonitorRename variable to $true to turn on each. Then within the switch statement of the script, add additional code to take the appropriate actions for your use case.

To monitor file type other than the example .txt, update the $MonitorFilter variable at the top of the script.

Cleanup

In this blog post, you created several components that may generate costs based on usage. To avoid incurring future charges, remove the following resources.

  1. Remove any AppStream 2.0 stacks created following this guide.
    1. Navigate to the AppStream 2.0 console.
    2. Select Stacks.
    3. Select the stack to delete and choose Action then Disassociate fleet.
    4. Choose Disassociate.
    5. Choose Action then Delete.
    6. Choose Delete.
  2. Remove any AppStream 2.0 fleets created following this guide.
    1. Navigate to the AppStream 2.0 console.
    2. Select Fleets.
    3. Select the fleet to delete and choose Action then Stop.
    4. Choose Stop.
    5. Wait for the fleet to show a Status of Stopped.
    6. Choose Action then Delete.
    7. Choose Delete.
  3. Remove any AppStream 2.0 image builders created following this guide.
    1. Navigate to the AppStream 2.0 console.
    2. Select Images.
    3. Select the Image Builder tab.
    4. Select the image builder to delete and choose Action then Delete.

Conclusion

In this blog, I showed you how to configure the AppStream 2.0 client and a file system watcher script to facilitate seamlessly exchange of files and information between local and remote applications.

In the provided example, a remote application automatically launches a local application when saving a new text file. To instead have a remote application react to a local file change, configure the filesystem watcher script in the AppStream 2.0 image. By modifying the provided sample script, applications can also react to other events such as file renames, deletes, or updates. You can also modify the script to take different actions based on the file extension. Use the provided sample to get you started and customize it to fit your use case and needs.

Thinking beyond the use case outlined in this article, the remote or local application could write information to a JSON or XML file that is parsed by the script. With that mechanism in place, this solution could be used for more advanced use cases beyond just opening a program.

Justin Grego is a Senior End User Computing Specialist Solutions Architect. As part of the EUC Service Aligned SA Team, he helps enable both customers and fellow SAs get up to speed on and be successful with new AWS EUC features and services.