Scripting

Frame Guest Agent (FGA) gives customers the ability to tailor the behavior of their workload VMs (e.g., Sandbox, shadow, production, utility server, etc.) to execute scripts at different event-points of a VM and/or user session. These events are defined as “lifecycle hooks.”

Once a Frame account is created, the Frame administrator can deploy custom scripts (PowerShell for Windows, Bash scripts for Linux) to the workload VMs via the Sandbox.

Custom Script Location

FGA scripts must be placed into a specific directory. The FGA Users Scripts directory for Windows VMs is:

C:\ProgramData\Nutanix\Frame\FGA\Scripts\User

Example valid script paths for Windows:

  • C:\ProgramData\Nutanix\Frame\FGA\Scripts\User\powershell\my_custom_script.ps1

  • C:\ProgramData\Nutanix\Frame\FGA\Scripts\User\my_custom_script.ps1

  • C:\ProgramData\Nutanix\Frame\FGA\Scripts\User\a\b\c\d\my_custom_script.ps1

For Linux VMs, the FGA Users Scripts directory is:

/opt/nutanix/frame/fga/scripts/user

Example valid script paths for Linux:

  • /opt/nutanix/frame/fga/scripts/user/powershell/my_custom_script.sh

  • /opt/nutanix/frame/fga/scripts/user/my_custom_script.sh

  • /opt/nutanix/frame/fga/scripts/user/a/b/c/d/my_custom_script.sh

Script Invocation

Invoking or executing custom scripts requires a definition.yml file (case sensitive) to be present in the FGA User Scripts directory. For example, this path for Windows would be C:\ProgramData\Nutanix\Frame\FGA\Scripts\User\definition.yml. FGA reads this file at each lifecycle hook and will invoke scripts as defined in this file.

Definition.yml

The definition.yml file describes detailed script context and execution information:

  • Script groups – a convenient way to group multiple scripts based on their context.

  • Lifecycle hooks or run-policies – phases in Frame’s lifecycle of VMs and Sessions.

  • Script paths and filenames – only use relative paths (from the FGA Users Script directory) or filenames. Look out for typos!

  • Pool-types – specifies which type of Frame VMs you’d like the scripts to execute on (Sandbox, Production, etc.)

  • Error policies – specifies the behavior FGA should follow if an error is encountered: continue or abort.

Below is an example of a definition.yml file.

groups:
-
  desc: Scripts on first boot
  name: first-boot
  timeout: 30
  run-policy: first-boot
  pool-type:
    - sandbox
    - production
  scripts:
    -
      desc: My script A
      path: A.ps1
      error-policy: continue
      timeout: 10
    -
      desc: My script B
      path: B.ps1
      error-policy: continue
      timeout: 10
-
  desc: Scripts on each boot
  name: every-boot
  timeout: 30
  run-policy: every-boot
  pool-type:
    - sandbox
    - production
  scripts:
    -
      desc: My script C
      path: C.ps1
      error-policy: continue
      timeout: 10
    -
      desc: My script D
      path: D.ps1
      error-policy: continue
      timeout: 10
-
  desc: Scripts before session is started
  name: pre-session
  timeout: 30
  run-policy: pre-session
  pool-type:
    - sandbox
    - production
  scripts:
    -
      desc: My script before sessions is started
      path: before-session.ps1
      error-policy: continue
      timeout: 20
-
  desc: Scripts after session is closed
  name: post-session
  timeout: 30
  run-policy: post-session
  pool-type:
    - sandbox
    - production
  scripts:
    -
      desc: My script after sessions is closed
      path: after-session.ps1
      error-policy: continue
      timeout: 20

Frame Session Lifecycle Hooks

The following table outlines various Frame Lifecycle Hooks, used as values for run-policy in a definition.yml script group. Each lifecycle hook is optional.

Hook Name

Description

User Context

first-boot

Executed only on the very first boot of the virtual machine, after it is created. This hook is available on all instances in a stateful flow, including shadow/production instances right after provisioning. It can be used to update the image before the non-persistence feature (for non-persistent VMs) is enabled for user sessions.

Frame user context.

every-boot

Executed on every boot and available only for stateless instances (non-persistent VMs).

Frame user context.

pre-apps

Executed every time before warming apps is started (only for workloads on AWS).

Frame user context.

post-apps

Executed every time after warming apps is completed (only for workloads on AWS).

Frame user context.

pre-session

Executed right before a user session is started.

Currently logged in user context. For example, if the workload VM is joined to the domain and the user authenticates to the Windows domain, then the scripts would run in the domain user context. Pre-session scripts are executed after the Windows login screen and before the onboarded application or desktop is displayed. Pre-session scripts never run in SYSTEM context.

post-session

Executed right after each session is closed.

Currently logged in user context.

pre-generalization

Executed before generalization process (which includes sysprep) is started.

Frame user context.

post-generalization

Executed after generalization process (which includes sysprep) is finished.

Frame user context.

on-idle

Executed when session goes to idle state (workload stops streaming).

Currently logged in user context.

on-active

Executed when session goes from idle to active state (workload resumes streaming).

Currently logged in user context.

pre-mount

Executed before a drive (enterprise profile disk, personal drive) is mounted.

Currently logged in user context.

post-mount

Executed after a drive (enterprise profile disk, personal drive) has been mounted.

Currently logged in user context.

pre-unmount

Executed before the unmounting of a drive (enterprise profile disk, personal drive) has started.

Currently logged in user context.

post-unmount

Executed after the unmounting of a drive (enterprise profile disk, personal drive) is done.

Currently logged in user context.

Script User Context (Windows)

It is important for Frame administrators to know which Windows user is running which script. Refer to the table above to see each lifecycle hook and their associated execution context. Frame’s default Frame user is a member of the local Windows administrator group.

To determine the user context, you can execute whoami within your script and it will print out the current script context.

Script Group Properties

Property Name

Description

desc

Description of the group

name

Name of the group

timeout

Integer value in seconds. Defines the maximum amount of time for all scripts executing within a group. If the execution time of the scripts exceeds the timeout value, FGA will kill the currently running script and skip the remainder of the scripts in the group.

run-policy

Defines the Frame Lifecycle Hook that will execute the scripts.

pool-type

Defines target pool types on which the scripts in the group are going to be applied. If none of the pool types is set, FGA will execute the script on all pool types.

scripts

Specifies the list of scripts that needs to be executed.

Pool-type Properties

  • sandbox - Scripts are going to be applied only on sandbox VMs

  • production - Scripts are going to be applied only on production VMs

  • shadow - Scripts are going to be applied only on shadow VMs that are part of the publishing process

  • utility - Scripts are going to be applied only on utility VMs

  • persistent-desktops - Scripts are going to be applied on persistent desktops

  • persistent-desktop-shadow - Scripts are going to be applied on freshly provisioned persistent desktops that are not yet assigned

Note

A “pool” in Frame’s context is a grouping of VMs associated with a Frame account.

Script item Properties

Property Name

Description

desc

Description of the script.

path

Script file location, relative to base user scripts directory.

error-policy

Defines FGA strategies when script fails.

timeout

Integer value in seconds. Defines the maximum amount of time for the script to run. If the execution time of the script exceeds the timeout value, FGA will kill the running script.

The error-policy property can have the one of the following values:

  • continue - FGA to proceed on even if the script fails.

  • abort - FGA to abort the task if script fails.

Getting Started

Follow these three simple steps to get started creating scripts and customizing the VMs on first boot (or every boot) and your Frame sessions.

1 - Create a simple script

Create a new PowerShell file (hello-world.ps1) in the FGA Scripts User directory.

# A simple message box with a warm greeting.
Add-Type -AssemblyName PresentationCore,PresentationFramework
[System.Windows.MessageBox]::Show('Hello, World!')

2 - Your definition.yml

Next, you need to communicate to FGA about this script via a definition.yml file to specify which Frame Session Lifecycle hook will trigger this script, timeout settings, the pool type(s), etc.

groups:
-
  desc: Simple script example
  name: A very simple pre-session script.
  timeout: 10
  run-policy: pre-session
  pool-type:
    - sandbox
  scripts:
    -
      desc: Frame Hello World Example script!
      path: hello-world.ps1
      error-policy: continue
      timeout: 10

In words, we have one script group that only executes during the pre-session lifecycle hook. We also specify that this group should only execute on the sandbox instance pool. Next, we specify a single script called hello-world.ps1. This script can execute for a maximum of 10 seconds before FGA will quit waiting and continue on.

3 - Testing and Debugging

Testing this is simple. If everything is correct, you should see this script’s dialog message when your next Sandbox session starts streaming.

Logging and Troubleshooting

Logging can be incredibly useful for debugging. However, if you’re using non-persistent desktops and trying to debug scripts in production, any logging you do on the VM is wiped out after the session.

Logging tips for various environments

  1. Sandbox and Persistent Desktops: Create any text file somewhere on C: with the output of your logs.

  2. Non-persistent Shadow and Production VMs: External logging service, either on the public internet (like Loggly, Splunk, etc.) or perhaps a service running on an accessible Utility Server.

Troubleshooting Tips

  • Always take backups before scripting.

  • If your scripts reference any file paths, be sure to use absolute filepaths, even for files residing in the FGA User Scripts folder.

  • If possible, test by executing the Powershell code in the Sandbox using PowerShell ISE.

  • If your scripts contain any sensitive information or credentials, be sure to write a cleanup pre-session script that can execute afterwards and delete the sensitive script/files before users are in session.

  • Use identifying elements from Frame environment variables to help understand who and when (if needed).

  • You can set and get environment variables or registry entries to help communicate between scripts and lifecycle hooks.

  • Avoid using error-policy: abort for sandbox scripts, as failing scripts can make the Sandbox inaccessible, requiring a restoration from a backup or termination (and recreation) of the Sandbox.

  • If your scripts need to run with Elevated Privilege in Windows 10, make sure that the below registry key value is set to 0. Otherwise, your scripts will fail due to Microsoft Windows User Account Controls (UAC) preventing the script from executing in an elevated context.

    • Key: SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System

    • Value: “EnableLUA”

    • Type: REG_DWORD

Environment Variables

FGA populates environment variables via registry entries to HKCU/Environment. These values are dynamically set for each Frame session and are useful when used with logging.

Env Variable

Description

FRAME_VENDOR_ID

A unique identifier tied to the Frame Account the VM is associated with.

FRAME_USER_EMAIL

The current session user’s email address.

FRAME_SESSION_ID

The unique Session ID associated with the current session. Useful for debugging and support purposes.

FRAME_SESSION_TOKEN

Unique token specific to the session. This token can be used to query user assertion/claims data, or base64 decoded to access various user and authentication-related data.

FRAME_VENDOR_EMAIL

Contrary to the name, this value is the unique GUID associated to the Frame account the VM belongs to.

Advanced Scripting

Update Script (Windows only)

If found, FGA will execute an “update” script right after a VM boots, but before FGA User scripts are executed. This allows Frame administrators to create custom update scripts to automated or dynamic maintenance operations on their custom scripts and files before user scripts defined in definition.yml are executed.

C:\ProgramData\Nutanix\Frame\FGA\Scripts\User\update.ps1

Updating the user scripts might include completely new packages of scripts or just a new definition.yml file. The following are best practices when it comes to updating your custom scripts using the Update script feature.

  1. Make sure your updated scripts and/or definition.yml are packaged in a bundle.

  2. Make sure your workload VMs are able to access and download that bundle.

  3. Create an update script that downloads the package and checks if there is a need for the update to be applied.

  • If not, the “update” script should just exit.

  • If yes, the “update” script should perform the update (it can delete existing definition.yml and all script files and then recreate definition.yml file and/or scripts).

  1. Place the update script content inside the update.ps1 (for Windows) and update.sh (for Linux).

When the VM gets into boot phase, FGA is going to search for the “update” script. If it does not find the script, FGA will read the definition.yml file and run the scripts as they are defined in the definition.yml file. If the FGA finds the “update” script, FGA will execute the script first. Once the script is executed, FGA will proceed to execute the custom scripts following the definition.yml file.

If the update script on Windows OS takes more than 10 minutes to execute, FGA will timeout and continue with its startup workflow.

Rebooting the VM (Windows only)

There may be a need to reboot the VM within one of the scripts. As a best practice, do NOT invoke a VM reboot using shutdown -r -t 0 (Windows) or sudo reboot (Linux). You will immediately stop the script execution flow and this action may result in a VM in an inconsistent state. Nutanix has provided a predefined “cmdlet” that should be used when the VM must be rebooted:

Invoke-Reboot

When Invoke-Reboot is executed, the cmdlet sends a reboot signal to FGA. FGA notifies Frame Platform of the reboot request and waits for a Frame Platform “ACK” message. Frame Platform marks the VM for reboot and sends the “ACK” message back to the FGA.

  • When the FGA receives the “ACK” message from Frame Platform, FGA will execute the OS reboot from within the VM (“soft reboot”). The OS will be rebooted and FGA restarted. FGA will then establish contact with Frame Platform and Frame Platform will notify the FGA to resume processing the remainder of the scripts in the group. WW: Not sure if this is correct.

  • If the FGA does not receive the “ACK” message within a specified period of time, FGA will resend the request again. FGA will never execute an operating system reboot until the FGA receives the “ACK” confirmation message from Frame Platform.

Note

If Frame Platform does not receive a response from the FGA after sending the “ACK” message after a specified period of time, Frame Platform will execute a hard reboot by calling the appropriate IaaS REST API to send a reboot request to the underlying cloud provider.

Example Scripts

Below are some example use cases where a custom script and associated definition.yml file can customize the end user experience.

Mount a Network Volume

Scenario: A Frame administrator would like to mount an existing read-only file share once a user starts a Frame session in either the Sandbox (Frame administrator) or a production workload VM (user).

The following simple PowerShell script “mount-read-only-volume.ps1” is placed in the scripts folder to be executed:

echo off
net use * /delete /Y
net use K: \\10.0.0.5\Fileshare /user:username password

The corresponding definition.yml file:

groups:
-
  desc: Scripts before session is started in either Sandbox or production workload VMs
  name: pre-session
  timeout: 30
  run-policy: pre-session
  pool-type:
    - sandbox
    - production
  scripts:
    -
      desc: Pre-session script to mount a read-only volume
      path: mount-read-only-volume.ps1
      error-policy: continue
      timeout: 20

Inclusion/Exclusion Rules for Enterprise Profiles

Scenario: A Frame administrator wishes to include and/or exclude specific folders from persisting in users’ enterprise profiles. The following PowerShell script “exclude-folders.ps1” is placed in the scripts folder to be executed:

# Include two separate folders C:\MyData and C:\MoreData to the user's enterprise profile.
Add-ProfileDiskInclusion -SourcePath C:\MyData
Add-ProfileDiskInclusion -SourcePath C:\MoreData

# Exclude four folders in the %USERPROFILE%
Add-ProfileDiskExclusion -SourcePath $env:USERPROFILE\Downloads -TargetPath C:\_Profile
Add-ProfileDiskExclusion -SourcePath $env:USERPROFILE\Music -TargetPath C:\_Profile
Add-ProfileDiskExclusion -SourcePath $env:LOCALAPPDATA\Autodesk -TargetPath C:\_Profile
Add-ProfileDiskExclusion -SourcePath $env:LOCALAPPDATA\Spotify -TargetPath C:\_Profile

The corresponding definition.yml file specifies that this PowerShell script should only be executed in the context of production workload VMs where the user’s enterprise profile volume is used (in a non-persistent Frame account):

groups:
-
  desc: Scripts before session is started in production workload VMs
  name: pre-session
  timeout: 30
  run-policy: pre-session
  pool-type:
    - production
  scripts:
    -
      desc: Pre-session script to mount a read-only volume
      path: exclude-folders.ps1
      error-policy: continue
      timeout: 20

Playing in the Sandbox

In this example, we’ll create a few more scripts, each set to execute at common session lifecycle hooks.

Warning

Before continuing with any changes to your sandbox, please make a backup first! Sandbox scripts, if improperly configured, can cause undesired behaviors and in some cases can cause the sandbox to become unresponsive, requiring restoration from a backup or a fresh sandbox.

To get started, grab your shovel and connect to your Sandbox. Then, open up your favorite text editor and create the following five files in the FGA User Scripts Directory.

Pre-session script: pre-session.ps1

To test this, the scripts will create or append to a single log file created at /logs/sandbox_bucket.txt with a timestamp and message from the script.

$RootPath = "C:\ProgramData\Nutanix\Frame\FGA\Scripts\User"
If (!(test-path "$RootPath\logs")){ New-Item -Path "$RootPath\logs" -Type Directory }
$timestamp = Get-Date -Format "dddd MM/dd/yyyy HH:mm K"
"$timestamp - Hello from pre-session.ps1" | Out-File "$RootPath\logs\sandbox_bucket.txt" -Append

Session Disconnect script: on-idle.ps1

$RootPath = "C:\ProgramData\Nutanix\Frame\FGA\Scripts\User"
If (!(test-path "$RootPath\logs")){ New-Item -Path "$RootPath\logs" -Type Directory }
$timestamp = Get-Date -Format "dddd MM/dd/yyyy HH:mm K"
"$timestamp - Hello from on-idle.ps1" | Out-File "$RootPath\logs\sandbox_bucket.txt" -Append

Session Resume script: on-active.ps1

$RootPath = "C:\ProgramData\Nutanix\Frame\FGA\Scripts\User"
If (!(test-path "$RootPath\logs")){ New-Item -Path "$RootPath\logs" -Type Directory }
$timestamp = Get-Date -Format "dddd MM/dd/yyyy HH:mm K"
"$timestamp - Hello from on-active.ps1" | Out-File "$RootPath\logs\sandbox_bucket.txt" -Append

Post-session script: post-session.ps1

$RootPath = "C:\ProgramData\Nutanix\Frame\FGA\Scripts\User"
If (!(test-path "$RootPath\logs")){ New-Item -Path "$RootPath\logs" -Type Directory }
$timestamp = Get-Date -Format "dddd MM/dd/yyyy HH:mm K"
"$timestamp - Hello from post-session.ps1" | Out-File "$RootPath\logs\sandbox_bucket.txt" -Append

Let’s walk through each line in these files.

  • Line 1: This line declares the root path as a variable variable. We could also use Set-Location.

  • Line 2: Creates the log folder inside of the root path if it doesn’t exist.

  • Line 3: Gets a timestamp in a readable format (based on the Sandbox’s local time).

  • Line 4: Writes our timestamp and hello message to the specified “sandbox_bucket” txt file.

Defining our scripts: definition.yml

We’ll create four different script groups (one for each lifecycle hook). These scripts will only execute on the Sandbox; each will have a total of 5 seconds to execute (timeout); and if these scripts fail for any reason, FGA will continue with the next lifecycle procedures.

groups:
-
  desc: Pre-session script
  name: Pre-session script group
  timeout: 5
  run-policy: pre-session
  pool-type:
    - sandbox
  scripts:
    -
      desc: Example sandbox pre-session script.
      path: pre-session.ps1
      error-policy: continue
      timeout: 5
-
  desc: On-idle disconnect script
  name: On-idle script group
  timeout: 5
  run-policy: on-idle
  pool-type:
    - sandbox
  scripts:
    -
      desc: Example sandbox on-idle script.
      path: on-idle.ps1
      error-policy: continue
      timeout: 5
-
  desc: On-active resume script
  name: On-active script group
  timeout: 5
  run-policy: on-active
  pool-type:
    - sandbox
  scripts:
    -
      desc: Example sandbox on-active script.
      path: on-active.ps1
      error-policy: continue
      timeout: 5
-
  desc: Post-session script
  name: Post-session script group
  timeout: 5
  run-policy: post-session
  pool-type:
    - sandbox
  scripts:
    -
      desc: Example sandbox post-session script.
      path: post-session.ps1
      error-policy: continue
      timeout: 5

Note

Notice that we’re only setting - sandbox for each pool-type. Publishing this Sandbox configured like this would ensure that these scripts will not execute on any other VM on this account (Shadow, non-persistent Production, Persistent VM instances).

Testing our Sandbox scripts

You made it this far, you have your scripts and definition.yml file in place on the Sandbox, great! Let’s test. Before we do, changes to the definition.yml are active in real-time. The Frame Guest Agent (FGA) does a fresh read of the definition.yml at each Session lifecycle hook. This means that if you are in your Sandbox session right now, we can test the disconnect and resume scripts by disconnecting from your Sandbox using the Gear menu at the bottom left corner of the Frame Session. Next, resume the session – we should see two new log entries in our /logs/sandbox_bucket.txt files.

Let’s test all four lifecycle hooks.

  1. This time, quit the session via the Gear menu. Wait for the session to fully close.

  2. Once you can, start a new session.

  3. Disconnect from the Gear Menu. This will fire our disconnect or “on-idle” script.

  4. Resume the Session. This will trigger our resume or “on-active” script.

  5. Close the session.

  6. Finally, start one more session to verify our sandbox_bucket.txt file contains messages and timestamps in order.

That’s it! If you run into any problems, here are a few basic troubleshooting steps before contacting Support:

  1. Please ensure that your files are in the correct paths.

  2. Please ensure that the file names match in the directory as well as in the definition.yml file.

  3. Test your PowerShell scripts manually from the Sandbox.

Cleanup: Once you’re done with testing these scripts and scenarios, all you need to do is remove the scripts from the definition.yml, delete the files, or customize them to your liking!