Fixing a Corrupt App-V Client (Updated)

I picked up a handy script recently that saved me a lot of time when fixing a corrupt App-V client. Kudos to Roy Essers with his post.

UPDATE – Also fixes the issue mentioned here related to App-V Error Code: 040000002C.

Essentially the issue occurs when a packager creates two packages with the same display name (and hence Package URI), but that have different version IDs. The App-V client gets confused, and even fails to work if you run a simple Get-AppvClientPackage command with a resultant:

Get-AppvClientPackage : Application Virtualization Service failed to complete requested operation.
Operation attempted: Get Packages.

As an example, you can navigate to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\AppV\Client\Streaming\Packages\

And you will no doubt find two subkeys (which are VersionIDs of packages) that contain a matching PackageURI registry value!

And the quick-fix is to run this script as an administrator – it will fix the corrupt App-V cache and initiate a publishing synchronisation as the logged in user:

cls
$logFile = "$env:TEMP\repair-app-v-client.log"
function Write-Log {
[CmdletBinding()]
param(
[string]$message
)
try {
Write-Host $message
Add-Content -Value "$((Get-Date).tostring('dd/MM/yyyy HH:mm:ss')) - $message" -Path $logFile
} catch {}
}
Write-Log "Repairing App-V Client.  Please Wait."
$corruptPackageFound = $false
$Catalog = "C:\Programdata\Microsoft\AppV\Client\Catalog\Packages"
Foreach($Package in (Get-ChildItem $Catalog)){ 
Foreach($Version in (Get-ChildItem (Join-Path $Catalog $Package.Name))){
$VersionID = $($Version.Name).Trim("{","}")
$PackageID = $(($Package.Name).Trim("{","}"))
$Manifest = "$Catalog\$($Package.Name)\$($Version.Name)\Manifest.xml"
[xml]$Content = Get-Content $Manifest
if((($Content.Package.Identity.VersionId).toUpper()) -ne $VersionID){
Write-Log "Found Corrupt Package:"
Write-Log "Name: $($Content.Package.Properties.DisplayName)"
Write-Log "PackageID: $PackageID"
Write-Log "VersionID: $VersionID"
$corruptPackageFound = $true
}       
}
if ($corruptPackageFound) {
#remove all packages with same packageid
#we iterate through folder structure and remove by packageid and versionid in case get-appvclientpackage still doesnt work
#we also dont just remove the corrupt package because it often reappears on sync
Write-Log "Cleaning package cache"
Foreach($Version in (Get-ChildItem (Join-Path $Catalog $Package.Name))){
$VersionID = $($Version.Name).Trim("{","}")
$PackageID = $(($Package.Name).Trim("{","}"))
Remove-AppvClientPackage -PackageId $PackageID -VersionId $VersionID | Out-Null
}
}
$corruptPackageFound = $false
}
#Fix App-V bug that breaks the client with App-V Error Code: 040000002C
$regKey = "HKLM:\SOFTWARE\Microsoft\AppV\Client\Virtualization\LocalVFSSecuredUsers"
Write-Log "Fixing corrupt entries in $regKey."
Get-Item $regKey | Select-Object -ExpandProperty property | ForEach-Object {
$name = $_
$val = (Get-ItemProperty -Path $regKey -Name $name).$_
if ($val -notlike "%USERPROFILE%*") {
#if it doesnt start with %USERPROFILE%, update it....
Set-ItemProperty -Path $regKey -Name $name -Value "%USERPROFILE%\AppData\Local\Microsoft\AppV\Client\VFS" -Type String -ErrorAction SilentlyContinue 
}
}
Write-Log "Re-start App-V Client Service."
net stop appvclient
net start appvclient
Write-Log "Re-synchronising App-V Applications."
#sync apps impersonating the logged in user
#create the action we want to perform
$command = 'powershell.exe'
$args = '-executionpolicy bypass -windowstyle hidden -command "Get-AppvPublishingServer | Sync-AppvPublishingServer"'
$action = New-ScheduledTaskAction -Execute $command -Argument $args
#create the scheduled task
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId (Get-CimInstance –ClassName Win32_ComputerSystem | Select-Object -expand UserName)
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal
$taskName = "AppvSync"
Register-ScheduledTask $taskName -InputObject $task | Out-Null
#start the scheduled task
Start-ScheduledTask -TaskName $taskName
#wait for the task to finish - check every 2 seconds or max 60 seconds
$timeout = 60 #seconds
$timer =  [Diagnostics.Stopwatch]::StartNew()
while (((Get-ScheduledTask -TaskName $taskName).State -ne  'Ready') -and  ($timer.Elapsed.TotalSeconds -lt $timeout)) {    
Write-Verbose  -Message "Waiting on scheduled task..."
Start-Sleep -Seconds 2
}
$timer.Stop()
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
Write-Log "Finished Repairing App-V Client."
Start-Sleep -Seconds 5

As ever, no warranty is provided with our scripts. Test, test and test again.