App-V 5 Scripting Context

I carried out some simple App-V 5 scripting tests recently so that I could prove which context the various App-V 5 scripts run in.

I created a very simple App-V 5 package containing Dependency Walker, and added a shortcut to the executable located at [{ProgramFilesX86}]\DependencyWalker\depends.exe.

I also added a script to the Scripts folder called alkane.ps1:

param([string]$message)
add-content "c:\temp\alkane.log" "$message $([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")) $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" -ErrorAction Stop

It’s a very simple PowerShell script that takes some text as an argument (I pass this to the script so that I know which scripting section it is running from).  Each time the script runs it will output the scripting section is has run from, whether the current scripting context is being run as an administrator, and also what account the script is running under.

In my DeploymentConfig.xml file I added the following UserScripts:

  <UserScripts>

      <StartProcess RunInVirtualEnvironment="true">
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig UserScripts StartProcess"</Arguments>
        <Wait RollbackOnError="true"/>
        <ApplicationId>[{ProgramFilesX86}]\DependencyWalker\depends.exe</ApplicationId>
      </StartProcess>

      <ExitProcess>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig UserScripts ExitProcess"</Arguments>
        <Wait RollbackOnError="false"/>
        <ApplicationId>[{ProgramFilesX86}]\DependencyWalker\depends.exe</ApplicationId>
      </ExitProcess>
      
      <StartVirtualEnvironment  RunInVirtualEnvironment="true">
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig UserScripts StartVirtualEnvironment"</Arguments>
        <Wait RollbackOnError="true"/>
      </StartVirtualEnvironment>
    
      <TerminateVirtualEnvironment>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig UserScripts TerminateVirtualEnvironment"</Arguments>
      <Wait RollbackOnError="false"/>
      </TerminateVirtualEnvironment>

      <PublishPackage>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig UserScripts PublishPackage"</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
      </PublishPackage>

      <UnpublishPackage>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig UserScripts UnpublishPackage"</Arguments>
        <Wait RollbackOnError="false" Timeout="30"/>
      </UnpublishPackage>
      
    </UserScripts>

and also the following MachineScripts:

 <MachineScripts>
      <PublishPackage>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig MachineScripts PublishPackage"</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
      </PublishPackage>

      <UnpublishPackage>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig MachineScripts UnpublishPackage"</Arguments>
        <Wait RollbackOnError="false" Timeout="30"/>
      </UnpublishPackage>
      
      <AddPackage>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig MachineScripts AddPackage"</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
      </AddPackage>
      <RemovePackage>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "DeploymentConfig MachineScripts RemovePackage"</Arguments>
        <Wait RollbackOnError="false" Timeout="60"/>
      </RemovePackage>
  </MachineScripts>

and in the UserConfig.xml file I added the following UserScripts:

 <UserScripts>

      <StartProcess RunInVirtualEnvironment="true">
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "UserConfig UserScripts StartProcess"</Arguments>
        <Wait RollbackOnError="true"/>
        <ApplicationId>[{ProgramFilesX86}]\DependencyWalker\depends.exe</ApplicationId>
      </StartProcess>

      <ExitProcess>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "UserConfig UserScripts ExitProcess"</Arguments>
        <Wait RollbackOnError="false"/>
        <ApplicationId>[{ProgramFilesX86}]\DependencyWalker\depends.exe</ApplicationId>
      </ExitProcess>
      
      <StartVirtualEnvironment  RunInVirtualEnvironment="true">
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "UserConfig UserScripts StartVirtualEnvironment"</Arguments>
        <Wait RollbackOnError="true"/>
      </StartVirtualEnvironment>
    
      <TerminateVirtualEnvironment>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "UserConfig UserScripts TerminateVirtualEnvironment"</Arguments>
      <Wait RollbackOnError="false"/>
      </TerminateVirtualEnvironment>

      <PublishPackage>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "UserConfig UserScripts PublishPackage"</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
      </PublishPackage>

      <UnpublishPackage>
        <Path>powershell.exe</Path>
        <Arguments>-ExecutionPolicy ByPass -File "[{AppVPackageRoot}]\..\Scripts\alkane.ps1" -message "UserConfig UserScripts UnpublishPackage"</Arguments>
        <Wait RollbackOnError="false" Timeout="30"/>
      </UnpublishPackage>

    </UserScripts>

The Test

In chronological order I added the package, published the package, launched the application, closed the application, unpublished the package and removed the package. I specified various user/deployment XML permutations during add and publish time. The results can be seen below.

The Results

Test 1

  • Apply DeploymentConfig.xml
  • Do NOT apply UserConfig.xml
  • Publish to user
Script File Script Section Script Type Run As Admin Run As User
DeploymentConfig MachineScripts AddPackage True System
DeploymentConfig UserScripts PublishPackage False User
DeploymentConfig UserScripts StartVirtualEnvironment False User
DeploymentConfig UserScripts StartProcess False User
DeploymentConfig UserScripts ExitProcess False User
DeploymentConfig UserScripts TerminateVirtualEnvironment False User
DeploymentConfig UserScripts UnpublishPackage False User
DeploymentConfig MachineScripts RemovePackage True System

Test 2

  • Apply DeploymentConfig.xml
  • Do NOT apply UserConfig.xml
  • Publish to machine
Script File Script Section Script Type Run As Admin Run As User
DeploymentConfig MachineScripts AddPackage True System
DeploymentConfig MachineScripts PublishPackage True System
DeploymentConfig UserScripts StartVirtualEnvironment False User
DeploymentConfig UserScripts StartProcess False User
DeploymentConfig UserScripts ExitProcess False User
DeploymentConfig UserScripts TerminateVirtualEnvironment False User
DeploymentConfig MachineScripts UnpublishPackage True System
DeploymentConfig MachineScripts RemovePackage True System

Test 3

  • Do NOT apply DeploymentConfig.xml
  • Apply UserConfig.xml
  • Publish to user
Script File Script Section Script Type Run As Admin Run As User
UserConfig UserScripts PublishPackage False User
UserConfig UserScripts StartVirtualEnvironment False User
UserConfig UserScripts StartProcess False User
UserConfig UserScripts ExitProcess False User
UserConfig UserScripts TerminateVirtualEnvironment False User
UserConfig UserScripts UnpublishPackage False User

Test 4

  • Do NOT specify DeploymentConfig.xml
  • Apply UserConfig.xml
  • Publish to machine

Error – cannot specify UserConfig.xml if published to machine (-global)

Test 5

  • Apply DeploymentConfig.xml
  • Apply UserConfig.xml
  • Publish to user
Script File Script Section Script Type Run As Admin Run As User
UserConfig UserScripts PublishPackage False User
UserConfig UserScripts StartVirtualEnvironment False User
UserConfig UserScripts StartProcess False User
UserConfig UserScripts ExitProcess False User
UserConfig UserScripts TerminateVirtualEnvironment False User
UserConfig UserScripts UnpublishPackage False User
DeploymentConfig MachineScripts RemovePackage True System
UserConfig UserScripts PublishPackage False User
UserConfig UserScripts StartVirtualEnvironment False User
UserConfig UserScripts StartProcess False User
UserConfig UserScripts ExitProcess False User
UserConfig UserScripts TerminateVirtualEnvironment False User
UserConfig UserScripts UnpublishPackage False User

Test 6

  • Apply DeploymentConfig.xml
  • Apply UserConfig.xml
  • Publish to machine

Error – cannot specify UserConfig.xml if published to machine

Summary

A summary of these results is depicted in the table below:

Script Script Section Script Type Can Run Inside Virtual Environment? Potentially RUNs Context
DeploymentConfig UserScripts StartProcess Yes Multiple User
DeploymentConfig UserScripts ExitProcess No Multiple User
DeploymentConfig UserScripts StartVirtualEnvironment Yes Multiple User
DeploymentConfig UserScripts TerminateVirtualEnvironment No Multiple User
DeploymentConfig UserScripts PublishPackage No Multiple User/System*
DeploymentConfig UserScripts UnpublishPackage No Multiple User/System*
DeploymentConfig MachineScripts PublishPackage No Multiple System
DeploymentConfig MachineScripts UnpublishPackage No Multiple System
DeploymentConfig MachineScripts AddPackage No Once System
DeploymentConfig MachineScripts RemovePackage No Once System
UserConfig UserScripts StartProcess Yes Multiple User
UserConfig UserScripts ExitProcess No Multiple User
UserConfig UserScripts StartVirtualEnvironment Yes Multiple User
UserConfig UserScripts TerminateVirtualEnvironment No Multiple User
UserConfig UserScripts PublishPackage No Multiple User
UserConfig UserScripts UnpublishPackage No Multiple User

Additional Notes

  • Administrative privileges are required to publish globally, as such globally published packages will be published using the system account
  • You can not specify a UserConfig.xml file when publishing globally
  • The UserScripts in UserConfig.xml will override the UserScripts in DeploymentConfig.xml if both config files are applied
  • Only the StartProcess and StartVirtualEnvironment script types have the option to run inside the virtual environment.
  • Only AddPackage and RemovePackage run once in a package lifecycle.  Every other script type has the potential to run multiple times, including (Un)PublishPackage (for example, if we use Set-AppVClientPackage to specify a new config file we will need to re-publish the package, but not re-add it.)
  • * All UserScripts specified in the DeploymentConfig file will run in a user context when published to a user.  However, when published to a machine all UserScripts specified in the DeploymentConfig file will run in a user context with the exception of PublishPackage and UnpublishPackage, which will run under a system context.

 

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!