Configuring RunVirtual Registry from the AppxManifest.xml

To configure the RunVirtual key from an App-V package we needed to add a registry key/value to the local machine via an App-V script.  Since our environment is VDI we are using a new feature in App-V 5 SP3 which enables us to target an App-V application which is published to the user (and not to the machine).

I won’t discuss what RunVirtual is – you can read this if you need to know.

The key reason for this tutorial is because we are performing this logic in the AppxManifest.xml and NOT the UserConfig.xml.  I prefer to add my logic to AppxManifest.xml since it makes my package self-contained with no reliance on external (xml) files – see here.

Why is adding RunVirtual registry logic to the AppxManifest.xml different from adding it to the UserConfig.xml?

Well first a very brief bit of background – the registry key/value we need to add is:

HKCU\SOFTWARE\Microsoft\AppV\Client\RunVirtual\{LocalProcess.exe}

(Default)     REG_SZ     {PackageId}_{VersionId}

By creating a key under RunVirtual with the process name (Excel.exe in this case) we effectively saying “whenever we run Excel.exe I want it to run in the same virtual space as the App-V package with the specified PackageId_VersionId”.  As you’ve probably gathered, this specific package was an Addin for Microsoft Excel.

Adding the logic via a UserConfig.xml script is relatively trivial.  We can open the XML file in a text editor and add the following:

   <UserScripts>
      <PublishPackage>
        <Path>cmd.exe</Path>
        <Arguments>/C REG ADD HKCU\SOFTWARE\Microsoft\AppV\Client\RunVirtual\Excel.exe /ve /d "2a04d2aa-ad7a-4c51-b774-00ca0e1e1fad_e2ce036d-9cbf-479d-9b67-8b4648742e8c" /f</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
      </PublishPackage>
      <UnpublishPackage>
        <Path>cmd.exe</Path>
        <Arguments>/C REG DELETE HKCU\SOFTWARE\Microsoft\AppV\Client\RunVirtual\Excel.exe /f</Arguments>
        <Wait RollbackOnError="false" Timeout="30"/>
      </UnpublishPackage>
    </UserScripts>

However adding a similar change via the AppxManifest.xml is slightly more complex.  Whenever we import a new AppxManifest.xml via the Advanced tab in Edit mode, we need to save the package afterwards for the change to take effect.  And what does this do?  It flips the VersionId of the package!  So we’re left with a situation whereby whenever we update the VersionId in the AppxManifest.xml it becomes redundant as soon as we save the updated package!

Introducing the Powershell approach….

Ignore the slightly different XML syntax for now – this is irrelevant in the context of this tutorial.

<appv:UserScripts>
<appv:PublishPackage>
<appv:Path>powershell.exe</appv:Path>
<appv:Arguments>-ExecutionPolicy ByPass -command "Get-AppvClientPackage -all | where {$_.Name -eq 'Alkane_Package'} | foreach { New-Item -Path HKCU:\SOFTWARE\Microsoft\AppV\Client\RunVirtual -Name Excel.exe -Value ($_.PackageId.toString() + '_' + $_.VersionId.toString()) -Force }"</appv:Arguments>
<appv:Wait RollbackOnError="false" />
</appv:PublishPackage>
<appv:UnpublishPackage>
<appv:Path>powershell.exe</appv:Path>
<appv:Arguments>-ExecutionPolicy ByPass -command "Remove-Item -Path HKCU:\SOFTWARE\Microsoft\AppV\Client\RunVirtual\Excel.exe -Recurse -Force"</appv:Arguments>
<appv:Wait RollbackOnError="false" />
</appv:UnpublishPackage>
</appv:UserScripts>

The main part to note is the Powershell one-liner, which I’ll explain below:

Get-AppvClientPackage -all | where {$_.Name -eq 'Alkane_Package'} | foreach { New-Item -Path HKCU:\SOFTWARE\Microsoft\AppV\Client\RunVirtual -Name Excel.exe -Value ($_.PackageId.toString() + '_' + $_.VersionId.toString()) -Force }

What we’re doing here is:

  • Getting all locally ADDED packages (Note that Get-AppvClientPackage -Name “Alkane_Package” will return nothing since the package is not published when the PublishPackage event runs!)
  • Looping through them where the Name attribute is the name of my target package (Alkane_Package)
  • We then use foreach because of this
  • And then we can dynamically get the PackageId and VersionId of the package to add it to the registry value

Scripting Examples for App-V 5

Below are a few scripting examples for App-V 5 configuration files (DeploymentConfig, UserConfig and AppxManifest files – the XML syntax varies slightly between these files but the same principle applies).  They’re purely for illustrative purposes and aim to show how we can string together the executable and their arguments to achieve different things.

**UPDATED WORKAROUND FOR MSIEXEC – Scroll down…**

Installing a shim

<MachineScripts>              
    <AddPackage>        
        <Path>sdbinst.exe</Path>        
        <Arguments>/q "[{AppVPackageRoot}]\..\Scripts\ExampleFile.sdb"</Arguments>      
        <Wait RollbackOnError="true" Timeout="30"/>      
    </AddPackage>   
</MachineScripts>

Running a Powershell script

<MachineScripts>
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -WindowStyle Hidden -File "[{AppVPackageRoot}]\..\Scripts\ExampleFile.ps1"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Running a Powershell command

<MachineScripts>
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -WindowStyle Hidden -Command  "&amp; { Get-EventLog -LogName security }"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Running an EXE (For example, Powershell) without waiting for execution to complete

<MachineScripts>
<AddPackage>
<Path>cmd.exe</Path>
<Arguments>/c START "" "powershell.exe" -ExecutionPolicy ByPass -WindowStyle Hidden -File "[{AppVPackageRoot}]\..\Scripts\runProcess.ps1"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Running a VBScript (via the CScript engine)

<MachineScripts>
<AddPackage>
<Path>cscript.exe</Path>
<Arguments>"[{AppVPackageRoot}]\..\Scripts\ExampleFile.vbs"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Running a VBScript (via the WScript engine)

<MachineScripts>
<AddPackage>
<Path>wscript.exe</Path>
<Arguments>"[{AppVPackageRoot}]\..\Scripts\ExampleFile.vbs"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Installing an MSI (if this doesn’t work see workaround below!)

<MachineScripts>
<AddPackage>
<Path>msiexec.exe</Path>
<Arguments>/i "[{AppVPackageRoot}]\..\Scripts\ExampleFile.msi" /qb</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Installing a Driver

<MachineScripts>
<AddPackage>
<Path>pnputil.exe</Path>
<Arguments>/i /a "[{AppVPackageRoot}]\..\Scripts\ExampleFile.inf"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Adding a Registry String Value

<MachineScripts>
<AddPackage>
<Path>reg.exe</Path>
<Arguments>ADD HKLM\SOFTWARE\AlkaneTestKey /f /v AlkaneTestValue /t REG_SZ /d AlkaneTestData</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Giving Modify Permission to a Directory

<MachineScripts>
<AddPackage>
<Path>icacls.exe</Path>
<Arguments>"C:\AlkaneTest" /grant Users:(OI)(CI)(M) /C /Q</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Creating a Symbolic Link (Running a windows command shell internal command – see here)

<MachineScripts>
<AddPackage>
<Path>cmd.exe</Path>
<Arguments>/C MKlink /D C:\AlkaneSolutions "C:\Program Files\AlkaneSolutions"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

 

**UPDATED WORKAROUND FOR MSIEXEC**

I referred a colleague of mine to this blog today.  He was trying to install an MSI from within the Scripts folder of his App-V 5 package.  But the example above didn’t work for him!!?  Event Viewer stated that MSIEXEC could not locate the MSI!?  I ran Process Monitor to confirm this.  And then I ran a quick local test.

I created two folders in the root of the C drive – C:\Scripts and C:\Root.  As you can probably tell, I was recreating the folder structure of an App-V 5 package to try and recreate the issue.

Essentially when we construct a path in an App-V 5 scripting node such as:

“[{AppVPackageRoot}]\..\Scripts\ExampleFile.msi”

When the package is added to a machine it will resolve to something like:

“C:\ProgramData\App-V\{PackageGUID}\{VersionGUID}\Root\..\Scripts\ExampleFile.msi”

Note how the path starts in the “Root” folder (“[{AppVPackageRoot}]”), goes up a level (“\..\”) and then goes into the Scripts folder.

To replicate this behaviour I placed a sample MSI in C:\Scripts called ExampleFile.msi and then opened up a command prompt and ran:

notepad.exe "c:\Root\..\Scripts\ExampleFile.msi"

The MSI opened in Notepad with no issues (full of gobbledygook as expected). I then ran:

msiexec.exe "c:\Root\..\Scripts\ExampleFile.msi"

Ooops!  The exact same path could not be resolved by the MSIEXEC engine!  So I assume MSIEXEC cannot handle the double-dot notation to go up one level in the directory structure?  As a workaround we had to run the MSI with a slightly different approach:

<MachineScripts>
    <AddPackage>
        <Path>cmd.exe</Path>
        <Arguments>/c cd "[{AppVPackageRoot}]\..\Scripts" &amp; msiexec.exe /i "ExampleFile.msi" /qn</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
    </AddPackage>
    <RemovePackage>
        <Path>cmd.exe</Path>
        <Arguments>/c cd "[{AppVPackageRoot}]\..\Scripts" &amp; msiexec.exe /x "ExampleFile.msi" /qn</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
    </RemovePackage>
</MachineScripts>

As ever, make a note of the timeout period (in seconds) if your MSI is likely to take longer to (un)install!  Also it might be worth setting RollbackOnError to “false” if your MSI is likely to request a (soft) reboot and return a 3010 (non-zero) error code!

AppxManifest.xml in place of DeploymentConfig.xml and UserConfig.xml

There’s a new feature in the App-V 5.1 Sequencer which enables us to export and import a file called AppxManifest.xml:

App-V 5.1 Sequencer Import/Export ManifestI’ve been using this approach recently to add custom scripting logic to my packages (I used ACE to make my life slightly easier).  What it means is that I can import my App-V package straight into the App-V Management Server and it works straight away with my custom changes – that is, I do not have to manually specify any Deployment/User config files using the approach below:

Appv-5 Overwrite ConfigIt also means that when I’m testing my application in standalone mode I do not have to specify DynamicUserConfigurationPath or DynamicDeploymentConfigurationPath parameters to apply the configurations to my package on the Powershell command line.