Desktop and Application Streaming
Bring your App-V packages to AppStream 2.0 with the dynamic application framework
Customers with existing Microsoft Application Virtualization (App-V) packages that are keen to use them with Amazon AppStream 2.0 can do so using the dynamic application framework. The dynamic application framework allows customers to build a custom application provider that dynamically adds applications to the AppStream 2.0 catalog.
This post details how to get started integrating App-V with AppStream 2.0 using the dynamic application framework.
Prerequisites
This tutorial assumes that you have the following installed and configured:
- An existing Microsoft Active Directory forest
- At least one App-V 5.1 publishing server (use the latest service release)
- At least one App-V Application package deployed to an Active Directory user
- The App-V client installer (if deploying to a Server 2012 R2 based image builder)
- An AppStream 2.0 image builder joined to your Microsoft Active Directory
In addition to the preceding requirements, you must have a group policy object with the following settings deployed to the organizational units in which the computer objects for your AppStream 2.0 streaming instances reside:
- Computer Configuration/Administrative Templates/System/App-V/Enable App-V Client
- Enabled (Only required if you are using 2016/2019 AppStream instances)
- Computer Configuration/Administrative Templates/System/App-V/Integration/Integration Root Global
- %allusersprofile%\Microsoft\AppV\Client\Integration
- Computer Configuration/Administrative Templates/System/App-V/Integration/Integration Root User
- %userprofile%\Documents\Microsoft\AppV\Client\Integration
- Computer Configuration/Administrative Templates/System/App-V/Publishing/Publishing Server 1 Settings
- Publishing Server Display Name <Server Display Name>
- Publishing Server URL http://<Server-FQDN>:Port#
- Global Publishing Refresh False
- Global Publishing Refresh On Logon False
- Global Publishing Refresh Interval 0
- Global Publishing Refresh Interval Unit Day
- User Publishing Refresh True
- User Publishing Refresh On Logon True
- User Publishing Refresh Interval 1
- User Publishing Refresh Interval Unit Day
Configuration of the AppStream 2.0 Image Builder
Before configuring the image builder, compile the required dynamic application framework and Apache Thrift dynamic link libraries (DLLs). For more information, see the Create a PowerShell-Based dynamic app provider in Amazon AppStream 2.0 post.
Installing the App-V client
When configuring a Windows Server 2012 R2 based AppStream image builder, you must install the App-V client manually. The App-V client is an integrated component of Server 2016 and 2019 OS, and does not require a manual App-V client install.
The installer for the App-V client can be found in your Microsoft Desktop Optimization Pack install media. You can use the following PowerShell commands to install the client as well as the latest service release for the client.
Start-Process appv_client_setup.exe -Wait -ArgumentList '/q /norestart /ACCEPTEULA /log C:\Windows\Logs\AppV51_Client_Install.log'
Start-Process AppV5.1RTM_Client_KB4074878.exe -Wait -ArgumentList '/q /norestart /ACCEPTEULA /log C:\Windows\Logs\AppV51_Client_HF02_Install.log'
You may have to perform a system restart after installing the client or its update packages.
PowerShell Script Configuration
Save the following two PowerShell scripts, as well as the dynamic application framework Client and Thrift DLLs in the folder C:\AppVSync.
The first script, appv-clientapps-usersync.ps1
, checks your App-V application entitlements and creates a CSV with the required application data for adding them to the AppStream 2.0 application catalog.
On completion, it creates a custom Windows Event Log that triggers the appv-clientapps-dafupdate.ps1
script. That script imports the data from the CSV and, using the AppStream 2.0 dynamic application framework APIs, adds the application to the catalog.
The following is the appv-clientapps-usersync.ps1
script:
Import-Module AppvClient
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | Out-Null
$logFile = "$home\Documents\Logs\AppStream\$env:username-$(get-date -f MM-dd-yyyy_HH_mm_ss)-appvsync.log"
New-Item -path $logfile -ItemType File -Force | Out-Null
Function Write-Log {
Param ([string]$message)
$stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
$logoutput = "$stamp $message"
Add-content $logfile -value $logoutput
}
Write-Log "Syncing with AppV Publishing Server..."
Sync-AppvPublishingServer $(Get-AppvPublishingServer).ID | Out-Null
Write-Log "Getting the user root path for the APPV VFS..."
$userrootpath = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\Policies\Microsoft\AppV\Client\Integration -Name IntegrationRootUser
Write-Log "Getting list of AppV application packages available for user $env:USERNAME..."
$names = Get-AppvClientPackage -All | Where-Object -Property IsPublishedToUser -eq "True" | Select-Object -ExpandProperty Name
If($null -eq $names){
Write-Log "There are no apps available to the user. Exiting..."
Exit
}
$count = 0
$csvpath = "$home\AppStream\AppVSync\appv-progslist.csv"
$columns = '"Id","DisplayName","LaunchPath","IconData"'
$iconpath = "$env:temp\icons"
If (!(test-path -path $iconpath)) {
Write-Log "Creating folder $iconpath..."
New-Item -path $iconpath -ItemType Directory -Force | Out-Null
}
foreach ($name in $names) {
Write-Log "Getting application display name, package ID and target path for package $name..."
$app_pid = Get-AppvClientPackage -Name $name | Select-Object -ExpandProperty PackageId | Select-Object -ExpandProperty GUID
$dname = (Get-AppvClientPackage -Name $name).getapplications() | Select-Object -ExpandProperty Name
$app_targetpath = (Get-AppvClientPackage -Name $name).getapplications() | Select-Object -ExpandProperty TargetPath
Write-Log "Cleaning up target path for $dname..."
If ($app_targetpath | Select-String -Pattern '[{ProgramFilesX86}]') {
$app_targetpath = $app_targetpath.replace('[{ProgramFilesX86}]', 'ProgramFilesX86')
}
elseif ($app_targetpath | Select-String -Pattern "[{ProgramFiles}]") {
$app_targetpath = $app_targetpath.replace('[{ProgramFiles}]', 'ProgramFiles')
}
Write-Log "Building the full path for application $dname..."
$user_fullpath = [System.Environment]::ExpandEnvironmentVariables("$userrootpath\$app_pid\Root\VFS\$app_targetpath")
Write-Log "Full path for $dname is $user_fullpath"
if (Test-Path -path $user_fullpath) {
Write-Log "$dname full path is valid..."
if (Test-Path -Path $csvpath) {
$count = (import-csv $csvpath | Measure-Object).Count
if (Select-String -SimpleMatch -Pattern "$dname" -Path $csvpath) {
Write-Log "$dname path already added to app list."
}
else {
$count = $count + 1
Write-Log "Getting $dname icon and converting it to Base64..."
[System.Drawing.Icon]::ExtractAssociatedIcon($user_fullpath).ToBitmap().Save("$iconpath\$dname.png")
$icondata = [convert]::ToBase64String((get-content "$iconpath\$dname.png" -Encoding byte))
Write-Log "Adding $dname application infomration to list..."
Add-Content -Path "$csvpath" -Value $("$($count),$($dname),$($user_fullpath),$($icondata)")
}
}
else {
$count = $count + 1
Write-Log "Getting $name icon and converting it to Base64..."
[System.Drawing.Icon]::ExtractAssociatedIcon($user_fullpath).ToBitmap().Save("$iconpath\$dname.png")
$icondata = [convert]::ToBase64String((get-content "$iconpath\$dname.png" -Encoding byte))
New-Item -path $csvpath -ItemType File -force | Out-Null
Add-Content -Path "$csvpath" -Value $columns
Write-Log "Adding $dname application infomration to list..."
Add-Content -Path "$csvpath" -Value $("$($count),$($dname),$($user_fullpath),$($icondata)")
}
}
else {
Write-Log "$dname path is not valid..."
}
}
Write-Log "Added $count application(s). Writing to event log..."
Write-EventLog -LogName AppVSync -source ClientRefresh -EntryType Information -eventID 25 -Message "Client Refresh for $env:username has completed successfully." -Verbose
Exit
The following is the appv-clientapps-dafupdate.ps1
script:
Add-Type -Path "C:\AppVSync\AS2DAF.dll"
Add-Type -Path "C:\AppVSync\Thrift.dll"
$currentuser = (get-wmiobject Win32_ComputerSystem).UserName.Split('\')[1]
$logfile = "C:\Users\$currentuser\Documents\Logs\AppStream\$currentuser-$(get-date -f MM-dd-yyyy_HH_mm_ss)-dafupdate.log"
New-Item -path $logfile -ItemType File -Force | Out-Null
Function Write-Log {
Param ([string]$message)
$stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
$logoutput = "$stamp $message"
Add-content $logfile -value $logoutput
}
$csvpath = "C:\Users\$currentuser\AppStream\AppVSync\appv-progslist.csv"
if (!(Test-Path -Path $csvpath)) {
Write-Log "No application catalog update file. Exiting..."
Exit
}
Write-Log "Opening connection to AS2 Thrift server..."
$transport = New-Object -TypeName Thrift.Transport.TNamedPipeClientTransport('D56C0258-2173-48D5-B0E6-1EC85AC67893')
$protocol = New-Object -TypeName Thrift.Protocol.TBinaryProtocol($transport)
$client = New-Object -TypeName AppStream.ApplicationCatalogService.Model.ApplicationCatalogService+Client($protocol)
$transport.open()
Write-Log "Connected..."
$usersid = (New-Object System.Security.Principal.NTAccount(($currentuser))).Translate([System.Security.Principal.SecurityIdentifier]).value
Write-Log "Getting applist for $currentuser, SID: $usersid..."
foreach ($app in (Import-Csv -path $csvpath)) {
$appId = $app.Id
$appdname = $app.DisplayName
$apppath = $app.LaunchPath
$appicon = $app.IconData
Write-Log "Adding $appdname..."
$applist = New-Object -TypeName AppStream.ApplicationCatalogService.Model.Application("$appId", "$appdname", "$apppath", "$appicon")
$getappreq = New-Object -TypeName Appstream.ApplicationCatalogService.Model.AddApplicationsRequest($usersid, $applist)
$client.AddApplications($getappreq)
}
Write-Log "Closing connection..."
$transport.close()
Write-Log "Cleaning up catalog file..."
Remove-Item -Path "C:\Users\$currentuser\AppStream" -Recurse -Force
Exit
Creating the Windows Event Log and Source
Create a custom Windows Event Log and source for the appv-clientapps-usersync.ps1
script to trigger the appv-clientapps-dafupdate.ps1
script. As an administrator, use the following PowerShell command to create both:
New-EventLog -LogName "AppVSync" -Source "ClientRefresh"
Import Windows Scheduled Tasks
Next, import the following scheduled tasks into the Windows Task Scheduler:
- App-V AppSync
- App-V DAF Update
The first task, App-V AppSync, executes the script appv-clientapps-usersync.ps1
when you log in to the streaming instance. The second task, App-V DAF Update, executes when the appv-clientapps-usersync.ps1
script creates the custom event. The task executes the appv-clientapps-dafupdate.ps1
script. For more information about Windows Task Scheduler, see Task Scheduler on the Microsoft Windows Dev Center website.
You can use the following PowerShell commands to import the tasks, or do the process manually through the Task Scheduler console.
Register-ScheduledTask -xml (Get-Content 'App-V AppSync.xml' | Out-String) -TaskName "App-V AppSync"
Register-ScheduledTask -xml (Get-Content 'App-V DAF Update.xml' | Out-String) -TaskName "App-V DAF Update"
The following is theApp-V AppSync.xml
file:
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Triggers>
<LogonTrigger>
<Enabled>true</Enabled>
</LogonTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<GroupId>S-1-5-32-545</GroupId>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
<UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
<Arguments>-NoProfile –NonInteractive -ExecutionPolicy Bypass -Command "& 'C:\AppVSync\appv-clientapps-usersync.ps1'"</Arguments>
</Exec>
</Actions>
</Task>
The following is the App-V DAF Update.xml
file:
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Triggers>
<EventTrigger>
<Enabled>true</Enabled>
<Subscription><QueryList><Query Id="0" Path="AppVSync"><Select Path="AppVSync">*[System[Provider[@Name='ClientRefresh'] and EventID=25]]</Select></Query></QueryList></Subscription>
</EventTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
<UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
<Arguments>-NoProfile –NonInteractive -ExecutionPolicy Bypass -Command "& 'C:\AppVSync\appv-clientapps-dafupdate.ps1'"</Arguments>
</Exec>
</Actions>
</Task>
Update the Agents.json config file
To enable dynamic app providers, update the Agents.json
config file, %programdata%\Amazon\AppStream\AppCatalogHelper\DynamicAppCatalog
.
The DisplayName
parameter must match a Display Name an installed application’s registry key in HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
.
This can be for any application and does not have to be related to AWS or App-V.
For more information, see Enable and Test Dynamic App Providers in the AppStream 2.0 documentation.
Enable dynamic app providers in Image Assistant
Launch the AppStream 2.0 Image Assistant. If you configured Agents.json
correctly, you should see the option to select Enable dynamic app providers under the Add Apps tab. Select it, then choose Next until you get to step 3. Test tab.
Testing integration functionality with a test user
To test the App-V integration functionality in your AppStream 2.0 image builder, use the switch user function and log in with an Active Directory user that has App-V application entitlements.
When you see the desktop, launch Image Assistant. After a few seconds, the App-V applications should appear. You can select one to launch it. If your image builder has specified locally installed applications, they also appear.
You can also check the log files located in %userprofile%\Documents\Logs\AppStream
to verify that the correct apps have been added.
Create AppStream 2.0 image from configured and tested Image Builder
Before creating your AppStream 2.0 image, use the following PowerShell commands to clean up the App-V cache. Run these commands as an administrator.
net stop AppVClient
Get-AppvClientPackage -All | Remove-AppVClientPackage
Remove-Item C:\ProgramData\App-V\* -recurse -Force
After cleaning up the App-V cache, open Image Assistant, and optionally add any apps you would like to include with the image. Proceed with the normal image creation process. You will always see local applications specified in the image, and you can’t manage them with the dynamic application framework.
Conclusion
That’s it! You now have an AppStream 2.0 image, with dynamic apps enabled, configured, and tested to integrate with App-V. If you have App-V application entitlements, you can now see your applications when you land on the AppStream 2.0 catalog page.