Application of the Week – PuTTY

PuTTY is a free SSH and telnet client, developed by Simon Tatham for the Windows platform. Because it’s a small application, it’s a great example demonstrating how App-V is supported in Liquit Workspace.

In this blog I’ve used an App-V package for PuTTY created using Liquit Setup Commander and the App-V Sequencer. That one used to be part of MDOP and is now part of the Windows 10 Assessment and Deployment Toolkit (Windows ADK).

To simplify creating a Liquit Workspace package for an App-V package of PuTTY, I’ve included a PowerShell script at the end of this blog.

This PowerShell script can be changed easily, to use it with any App-V package you would like to distribute and use from within Liquit Workspace. It creates a Smart Icon with all actions needed to enable App-V, publish, unpublish and remove the App-V package for PuTTY as demonstrated in this video:

The actions which are used to support App-V have been divided in an Install, Launch and Uninstall action set:

  • Install uploaded PuTTY 0.73.appv

    This action copies ‘PuTTY 0.73.appv’ to ${PackageTempDir}PuTTY 0.73.appv. This directory translates to %LOCALAPPDATA%Temp

  • Enable-Appv

    This action uses the Enable-Appv cmdlet to enable the Microsoft Application Virtualization (App-V) service on Windows 10 Enterprise or Education 1607 and up.

  • Add-AppvClientPackage | Mount-AppvClient

    This action uses the Add-AppvClientPackage cmdlet to add the App-V application per device on devices which have App-V enabled.

  • Mount-AppvClientPackage (not shown in the video)

    This action uses the Mount-AppvClientPackage cmdlet to load the package into the App-V cache on devices which have App-V enabled.

  • Publish-AppvClientPackage

    This action uses the Publish-AppvClientPackage cmdlet to publish the application for all users of a device.

  • Start Normal PuTTY

    If needed, you can enable this action to launch PuTTY in a normal way for devices on which App-V is not supported like Windows 10 Home Edition. An ‘Install package’ action, referencing a managed package for PuTTY coming from the Setup Store, needs to be added to the Install action set then. But that’s out of the scope for this blog, because I only wanted to focus on using App-V.

  • Start App-V PuTTY

    After the App-V application has been published the application is available as App-VE1D03455-9A27-4E26-BF4E-CAA2F2DD8A239980FADE-CD94-4C81-B9B2-32F8FF2BCACCRootVFSProgramFilesX86PuTTYputty.exe in the All Users Profile directory.

    A filter to check for ${AllUsersProfile}App-VE1D03455-9A27-4E26-BF4E-CAA2F2DD8A239980FADE-CD94-4C81-B9B2-32F8FF2BCACCRootVFSProgramFilesX86PuTTYputty.exe has been added to make sure it’s only started when it has been published.

  • Unpublish-AppvClientPackage PuTTY

    This action uses the Unpublish-AppvClientPackage cmdlet to unpublish the published App-V application for all users of a device.

  • Remove-AppvClientPackage PuTTY

    This action uses the Remove-AppvClientPackage cmdlet to unpublish the published App-V application for all users of a device.

Import-Module "C:Program Files (x86)Liquit WorkspacePowerShell3.0Liquit.Server.PowerShell.dll" -Prefix "Liquit"

[System.Reflection.Assembly]::LoadWithPartialName(“System.IO.Compression”) | Out-Null;
[System.Reflection.Assembly]::LoadWithPartialName(“System.IO.Compression.FileSystem”) | Out-Null;

$liquitZoneUsername = "localadmin"
$liquitZonePassword = Read-Host "Enter Password" -AsSecureString
$liquitCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $liquitZoneUsername, $liquitZonePassword
$liquitZone = "https://yourliquitzone.liquit.com"

$puttyIconURL = "https://www.setupcommander.com/ico/putty.ico"
$puttyIconPath = "c:windowstempputty.ico"
$puttyAppVPackagePath = "c:packagesPuTTY 0.73.appv"
$puttyAppVPackageFilename = "PuTTY 0.73.appv"

#get the Version ID and Package ID of the App-V package
$AppxManifestPath = "c:windowstempappxmanifest.xml"
$AppV5Package = New-Object System.IO.Compression.ZipArchive(New-Object System.IO.FileStream($puttyAppVPackagePath, [System.IO.FileMode]::Open));
$AppxManifest = $AppV5Package.GetEntry(“AppxManifest.xml”);
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($AppxManifest, $AppxManifestPath, $true);
$xml = [xml](Get-Content $AppxManifestPath)
$AppV5PackageVersionId = $xml.Package.Identity.VersionId.ToUpper() 
$AppV5PackageId = $xml.Package.Identity.PackageId.ToUpper()
$AppV5Version = $xml.Package.Identity.Version
$AppV5DisplayName = $xml.Package.Properties.DisplayName
$AppV5Package.Dispose();

#connect to Liquit Workspace
$liquitContext = Connect-LiquitWorkspace -URI $liquitZone -Credential $liquitCredentials

#download the icon
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile($puttyIconURL,$puttyIconPath)
$iconContent = New-LiquitContent -Path $puttyIconPath

#define and create the package 
$packageName = "PuTTY"
$packageDisplayName = "PuTTY"
$packageDescription = "PuTTY"

$package = New-LiquitPackage -Name $packageName `
                             -Type "Launch" `
                             -DisplayName $packageDisplayName `
                             -Description $packageDescription `
                             -Priority 100 `
                             -Enabled $true `
                             -Offline $True `
                             -Web $false `
                             -Icon $iconContent

#create the snapshot for this package
$snapshot = New-LiquitPackageSnapshot -Package $package

#define install action set
$actionset_install = New-LiquitActionSet -snapshot $snapshot `
                                 -Type Install `
                                 -name 'Install' `
                                 -Enabled $true `
                                 -Frequency Always `
                                 -Process Sequential


#
#define the first install action
$actionset_install_action1 = New-LiquitAction -ActionSet $actionset_install `
                                      -Name 'Copy uploaded App-V Package to local' `
                                      -Type 'contentcopy' `
                                      -Enabled $true `
                                      -IgnoreErrors $false `
                                      -Settings @{content = $puttyAppVPackageFilename; destination = '${PackageTempDir}' + $puttyAppVPackageFilename} `
                                      -Context Device

$appvContent = New-LiquitContent -Path $puttyAppVPackagePath -FileName $puttyAppVPackageFilename
$attribute = New-LiquitAttribute -Entity $actionset_install_action1 -Link $appvContent -ID 'content' -Settings: @{filename = $puttyAppVPackageFilename}


#
#define the second install action
#
$actionset_install_action2 = New-LiquitAction -ActionSet $actionset_install `
                                      -Name 'Enable-Appv' `
                                      -Type 'contentscript' `
                                      -Enabled $true `
                                      -IgnoreErrors $false `
                                      -Settings @{content = 'script.ps1'; type = 3; display = 1; engineParameters = "-NoLogo -ExecutionPolicy Bypasss"; successReturnCodes = 0, 1} `
                                      -Context Device

#add the PowerShell script for this action
$scriptLine = 'Enable-Appv'
Set-Content -Path 'c:windowstempscript.ps1' -Value $scriptLine
$script_content_actionset_install_action2 = New-LiquitContent -Path "c:windowstempscript.ps1" -FileName 'script.ps1'
$attribute = New-LiquitAttribute -Entity $actionset_install_action2 -Link $script_content_actionset_install_action2 -ID 'script' -Settings: @{filename = 'script.ps1'}

#
#define the third install action
#
$actionset_install_action3 = New-LiquitAction -ActionSet $actionset_install `
                                      -Name 'Add-AppvClientPackage' `
                                      -Type 'contentscript' `
                                      -Enabled $true `
                                      -IgnoreErrors $false `
                                      -Settings @{content = 'script.ps1'; type = 3; display = 1; engineParameters = "-NoLogo -ExecutionPolicy Bypasss" ; directory = '${PackageTempDir}'; successReturnCodes = 0, 1} `
                                      -Context Device

#add the PowerShell script for this action
$scriptLine = 'Add-AppvClientPackage "' + $puttyAppVPackageFilename + '"'
Set-Content -Path 'c:windowstempscript.ps1' -Value $scriptLine
$script_content_actionset_install_action3 = New-LiquitContent -Path "c:windowstempscript.ps1" -FileName 'script.ps1'
$attribute = New-LiquitAttribute -Entity $actionset_install_action3 -Link $script_content_actionset_install_action3 -ID 'script' -Settings: @{filename = 'script.ps1'}


#
#define the fourth install action
#
$actionset_install_action4 = New-LiquitAction -ActionSet $actionset_install `
                                      -Name 'Mount-AppvClientPackage' `
                                      -Type 'contentscript' `
                                      -Enabled $true `
                                      -IgnoreErrors $false `
                                      -Settings @{content = 'script.ps1'; type = 3; display = 1; engineParameters = "-NoLogo -ExecutionPolicy Bypasss" ; directory = '${PackageTempDir}'; successReturnCodes = 0, 1} `
                                      -Context Device

#add the PowerShell script for this action
$scriptLine = 'Mount-AppvClientPackage "' + $puttyAppVPackageFilename + '"'
Set-Content -Path 'c:windowstempscript.ps1' -Value $scriptLine
$script_content_actionset_install_action4 = New-LiquitContent -Path "c:windowstempscript.ps1" -FileName 'script.ps1'
$attribute = New-LiquitAttribute -Entity $actionset_install_action4 -Link $script_content_actionset_install_action3 -ID 'script' -Settings: @{filename = 'script.ps1'}


#
#define the fifth install action
#
$actionset_install_action5 = New-LiquitAction -ActionSet $actionset_install `
                                      -Name 'Publish-AppvClientPackage' `
                                      -Type 'contentscript' `
                                      -Enabled $true `
                                      -IgnoreErrors $false `
                                      -Settings @{content = 'script.ps1'; type = 3; display = 1; engineParameters = "-NoLogo -ExecutionPolicy Bypasss" ; directory = '${PackageTempDir}' ; successReturnCodes = 0, 1} `
                                      -Context Device

#add the PowerShell script for this action
$scriptLine = 'Publish-AppvClientPackage -Name "' + $AppV5DisplayName + '" -Global'
Set-Content -Path 'c:windowstempscript.ps1' -Value $scriptLine
$script_content_actionset_install_action5 = New-LiquitContent -Path "c:windowstempscript.ps1" -FileName 'script.ps1'
$attribute = New-LiquitAttribute -Entity $actionset_install_action5 -Link $script_content_actionset_install_action5 -ID 'script' -Settings: @{filename = 'script.ps1'}

#define launch action set
$actionset_launch = New-LiquitActionSet -snapshot $snapshot `
                                 -Type Launch `
                                 -name 'Launch' `
                                 -Enabled $true `
                                 -Frequency Always `
                                 -Process StopAtFirstEffectiveAction
#
#define the first launch action
#
$actionset_launch_action1 = New-LiquitAction -ActionSet $actionset_launch `
                                      -Name 'Start Normal PuTTY' `
                                      -Type 'processstart' `
                                      -Enabled $false `
                                      -IgnoreErrors $false `
                                      -Settings @{name = '${ProgramFiles32}PuTTYputty.exe'; parameters = "";} `
                                      -Context User

#define the filter set for the first launch action
$filterset_launch_action1 = New-LiquitFilterSet -Action $actionset_launch_action1

#add a filter to the first action
$filter_launch_action1 = new-LiquitFilter -FilterSet $filterset_launch_action1 -type fileexists -Settings @{path = '${ProgramFiles32}PuTTYputty.exe';} -Value "true" 

#
#define the second launch action
#
$actionset_launch_action2 = New-LiquitAction -ActionSet $actionset_launch `
                                      -Name 'Start App-V PuTTY' `
                                      -Type 'processstart' `
                                      -Enabled $true `
                                      -IgnoreErrors $false `
                                      -Settings @{name = '${AllUsersProfile}App-V' + $AppV5PackageId + '' + $AppV5PackageVersionId + 'RootVFSProgramFilesX86PuTTYputty.exe'; parameters = "";} `
                                      -Context User

#define the filter set for the second launch action
$filterset_launch_action2 = New-LiquitFilterSet -Action $actionset_launch_action2

#add a filter to the second action
$filter_launch_action2 = new-LiquitFilter -FilterSet $filterset_launch_action2 -type fileexists -Settings @{path = '${AllUsersProfile}App-V' + $AppV5PackageId + '' + $AppV5PackageVersionId  + 'RootVFSProgramFilesX86PuTTYputty.exe';} -Value "true" 


#define uninstall action set
$actionset_uninstall = New-LiquitActionSet -snapshot $snapshot `
                                 -Type Uninstall `
                                 -name 'Uninstall' `
                                 -Enabled $true `
                                 -Frequency Always `
                                 -Process Sequential
#
#define the first uninstall action
#
$actionset_uninstall_action1 = New-LiquitAction -ActionSet $actionset_uninstall `
                                      -Name 'Unpublish-AppvClientPackage PuTTY' `
                                      -Type 'contentscript' `
                                      -Enabled $true `
                                      -IgnoreErrors $false `
                                      -Settings @{content = 'script.ps1'; type = 3; display = 1; engineParameters = "-NoLogo -ExecutionPolicy Bypasss"} `
                                      -Context Device

#add the PowerShell script for this action
$scriptLine = 'Unpublish-AppvClientPackage -Name "' + $AppV5DisplayName + '" -Version ' + $AppV5Version + ' -Global'
Set-Content -Path 'c:windowstempscript.ps1' -Value $scriptLine
$script_content_actionset_uninstall_action1 = New-LiquitContent -Path "c:windowstempscript.ps1" -FileName 'script.ps1'
$attribute = New-LiquitAttribute -Entity $actionset_uninstall_action1 -Link $script_content_actionset_uninstall_action1 -ID 'script' -Settings: @{filename = 'script.ps1'}

#
#define the second uninstall action
#
$actionset_uninstall_action2 = New-LiquitAction -ActionSet $actionset_uninstall `
                                      -Name 'Remove-AppvClientPackage PuTTY' `
                                      -Type 'contentscript' `
                                      -Enabled $true `
                                      -IgnoreErrors $false `
                                      -Settings @{content = 'script.ps1'; type = 3; display = 1; engineParameters = "-NoLogo -ExecutionPolicy Bypasss"} `
                                      -Context Device

#add the PowerShell script for this action
$scriptLine = 'Remove-AppvClientPackage -PackageId ' + $AppV5PackageId.Tolower() + ' -VersionId ' + $AppV5PackageVersionId.ToLower()
Set-Content -Path 'c:windowstempscript.ps1' -Value $scriptLine
$script_content_actionset_uninstall_action2 = New-LiquitContent -Path "c:windowstempscript.ps1" -FileName 'script.ps1'
$attribute = New-LiquitAttribute -Entity $actionset_uninstall_action2 -Link $script_content_actionset_uninstall_action2 -ID 'script' -Settings: @{filename = 'script.ps1'}


#publish the package
Publish-LiquitPackageSnapshot -Snapshot $snapshot -stage Production 

#set the entitlement
$identity = Get-LiquitIdentity -id "LOCALeveryone"
New-LiquitPackageEntitlement -Package $package -Identity $identity -Publish Workspace


NB. App-V is only supported on Windows 10 Education and Enterprise. If you use Windows 10 Home or Professional in your organization, besides Windows 10 Enterprise, you can filters to the Install and Uninstall actionsets. The two filters below can be used to check for ‘Windows 10.0’ and the value of the CompositionEditionID in the registry which is ‘Enterprise’ when Windows 10 Enterprise is used: