In this article, I want to give a brief lesson on something called OSD scripting. OSD scripting is a way to add dynamic or added functional behavior into App-V packages outside what the App-V itself as a virtualization technology provides. An example of such functionality could be things like network drive mappings, modification into the local system (very dependent on user’s access rights of course) or pre application launch pre-requisites checking.
Like the topic already reveals, this scripting functionality needs to be added into one or more OSD files in an App-V package and thus is tied into runtime “lifecycle” of one specific application published out of the package.
In other words, you cannot apply scripting to whole of the package – at least not without some additional tweaking of your package – even if the requirement would be like that, such as when you want to do some massaging of the underlying system before user launches an application out of possibly multiple ones published from that package (so it’s nondeterministic for you, as a packager, which one of them you attach your script to). To overcome this, you usually would copy the same script over to multiple applications to make sure it will execute regardless what gets launched first. Sadly, doing so in the Sequencer UI is not directly supported and so is much easier “by hand” in the text editor.
Another lifecycle –related restriction of scripting is that you cannot trigger App-V package (OSD) scripting for operations performed in the App-V Client Management Console; like for instance when application is removed from the client (that would actually be very useful if it could be done, as way to do cleanups of changes against real system).
Timing of the script
Still keeping on topic of the lifecycle of the application you add the scripting into; each script needs to have information on when exactly it needs to be executed regarding the lifecycle of that one virtual application represented by the OSD file. There are three possible execution points with two variations of each, minus one:
- Before the package contents are streamed from the server to the client
- After the package contents has been streamed and cached to the client
- Just before the main application process is started (i.e. [usually] the executable specified with FILENAME=”…” attribute)
- After the main application process has been started
- After the main application process has exited from the process list
Note that for the first two, if the package has already been cached on the client, these both happen pretty much immediately upon double-clicking the application icon but there’s still relevance on the ordering: before streaming attached scripts do execute before after streaming attached scripts.
Depending on your exact need, you have to select from one of these points to attach your script to. For instance, if you intend to map network drives for an application, it makes sense to run script at the point before the application has actually started…
Scope and type of script execution
In addition to providing information on when to start your script, you need to tell App-V few other things regarding on how you want you script to be run.
First one of these things is to set the stage on what the script can touch on the system.
As we know, virtual applications are run inside the bubble – or container – which protects the underlying system from registry changes and modifications into fully virtualized directories; along with presenting virtual assets in the package to the processes running in that bubble (that’s the “virtualization” part of application virtualization).
With scripting, it’s needed on some occasions to have access to those virtualized assets just like virtual applications from the package do – to modify application’s configuration file, for instance – and other times it is better if we run the script outside that container – like when needing to change settings in the real registry. To accommodate this varying need, there’s attribute in the OSD scripting that tells either to protect (i.e. run inside the bubble) or not protect (i.e. run outside the bubble) the script. This way we can control what resources the script will see and have access to.
Another setting that we must define for scripting is how the execution of the script relates to the overall lifecycle management for application. What I mean by this is that we can run the script synchronously or asynchronously; the difference being that the execution of synchronous script will be waited upon by App-V Client before moving forward to the next stage. As an example, if you run script synchronously before the main virtual application is launched, then the actual application won’t start until your script has exited its execution. But, on the other hand, if you run the same script asynchronously, then App-V Client just shoots the script into action and goes merrily forward without waiting for the results.
Now, there’s actually two separate implementation mechanisms for this waiting or non-waiting of the script’s execution when setting the parameters for scripting: You can either just say that script is run synchronously and it will time out after default period of time (3 minutes or so, IIRC) or that it’s run asynchronously. Or you could set explicit timeout value [in seconds] for synchronous execution, in which case you don’t use that other flag (we’ll see the actual elements and attributes later on in this article). But the thing is, you use one method or the other, but not both two (it would be little bit silly to say that you want to run script synchronously and then set the timeout, latter one implies the former one already).
Please note that there are certain combinations of timing, protection and/or script’s execution type that App-V Client does not really support. These are:
- Running scripts inside the bubble beforestreaming – this would be impossible because the virtual environment configuration (potentially) has not been streamed in yet. Behaviour of the client changes little bit depending what type of script is run, and you may or may not succeed starting the script without error message (see below for what you would get if you don’t), but it is entirely possible that you do not have access to any virtual assets from the package at this stage. So very nondeterministic once again!
- Running scripts asynchronously inside the bubble after streaming –the same error message as above, but only for asynchronous scripts (of certain type); synchronous scripts are not affected.
- Running script asynchronously inside the bubble after application exit – the script will run, yes, but only few microseconds before App-V Client destroys the virtual environment around it, terminating the script process as it goes.
Additional settings for scripting
In addition to basic, mandatory, parameters above for running any custom scripting in the OSD file, App-V support settings two numerical values for the purpose of allowing control of application’s lifecycle based on scripts execution results. To put that once again in concrete terms, you can use scripting to stop application from launching or conversely allow application to launch based on what your script returns as sometimes commonly known as error result value.
Error result value is numerical code that each and every process in Windows can return to operating system, indicating that everything went just okay or that error happened during execution. Since it’s only a numerical value, it’s up to the caller of that application – be that a script file or another application – to interpret it and determine if something should be done about the issue.
This numerical value can also be examined by the App-V Client when the script execution ends (and here we are assuming synchronous execution and script timing points before main application launch), and either stop further processing if returned value matches to one you specify in script’s setting or stop further processing if returned value does not match. There are two different, mutually exclusive, parameters for this in the OSD scripting.
Two different types of OSD script
I think it’s a bit misnomer to be talking about script types (usually indicating scripting language or some such concept), when what we are really talking about is two technical implementations of how to get your script executed by App-V Client from the OSD file. But let’s call them types of script for argument’s sake.
First type of the script you can have is built-in Windows command interpreter script, which shall be called as scriptbody -script based on the actual XML element used for the purpose. Scriptbody scripts are literal .bat/.cmd command scripts embedded directly in the OSD files, and the way App-V Client executes them is to create a temporary script file into one of App-V Client’s data directories and pass execution of it to cmd.exe.
With scriptbody scripts there is added hindrance of having the need to double-quote backslash characters with another backslash (think: \\server\share becomes to \\\\server\\share), and to indicate physical line ends with \n –character when you write those scripts into your OSD file. For programmers out there, it is very obvious that these requirements are something that could maybe be categorized as leaky abstractions. But double-quote and backslash-n we must if wanting to do scriptbody -scripting, so nothing we can do about that leaking abstraction!
A contrived example of scriptbody script (without enclosing element of OSD itself) could be the following:
@echo off \n
if exist s: goto copyit \n
net use s: \\\\appvserver\\content \n
:copyit \n
copy /y s:\\saplogon.ini %appdata%\\SAP\\ \n
net use s: /d \n
As you can see, having the need to add extra backslashes and explicit line terminations using \n can make script very hard to read and write very quickly!
Second type of script is actually not script at all, but rather a mechanism to start any arbitrary executable of choice. Continuing the naming, we call this Href –script and it simply takes a form of a path (and optionally parameters) that instructs App-V Client to launch whatever is behind this path. Using href scripting you can actually use your scripting language of choice by starting the appropriate script interpreter (like wscript.exe, or powershell.exe) with the script file as a parameter. Or it could be just an ordinary application that you launch which does some processing.
Since the href script is only a pointer to a script in an already pre-existing file and not the script’s contents per se like with scriptbody, you will need to stage the script file somewhere outside the bubble (usually network path) or inside the bubble (create a file inside the package’s directory structure on Q: drive) and call from that location.
In the latter case, if the script file has the need to be executed outside the virtual environment, it can become very complex to set it up things so that you could have the file shipped with the package itself i.e. to have self-contained package without requirements of any external network locations (think about laptops in outside the organisation’s network, for instance) for script file storage.
As another contrived example, here’s what could be called as href script (again without enclosing OSD syntax):
%windir%\SysWOW64\cscript.exe //nologo //e:vbs %sft_mnt%\mypackage\establish_user_profile.vbs
Note that with href script you fortunately won’t need those escaping rules that scriptbody scripting has, so everything is written as-is. And that there’s actually a special environment variable available for OSD scripts that denotes the drive letter for App-V’s virtual package drive.
Moving things into practice
Okay, now, having been discussing about the fundamentals of OSD scripting and the different elements of what the scripting in App-V is made out of, why don’t we actually move into practice and see just how all of these things are put together to make OSD file that executes out scripting!
There’s basically two ways for you to add script into your existing package; using “OSD Editor” in the Sequencer or editing OSD files directly with suitable text editor (I prefer Notepad++ which is very able and, best of all, a free text editor; just remember to add OSD as custom extension into style configurator configuration so it will automatically use XML highlighting for OSD files).
I think both of these methods has their own merits, like the fact that using Sequencer also updates the copy of OSD inside the deployment MSI file if you use those, but it is generally faster to use text editor if you know what you are doing. Another big plus for text editor is the fact that we can easily copy-paste script blocks between several OSD files if you have package that has many different application needing the same scripts like we discussed in the beginning of this article.
Looking at the Sequencer method of adding new script, let’s open a package for editing and go into OSD –tab:
When in the OSD tab, we select the application we want to add script(s) to and expand DEPENDENCY –node:
By default DEPENDENCY node does not have anything besides dependency setting for App-V Client’s version, so let’s add a new SCRIPT element to it. <SCRIPT></SCRIPT> is our XML block for defining script’s overall settings.
After creating a new script element, we have five attributes already pre-created and listed when selecting the element. Four of these attributes – TIMING, EVENT, PROTECT and WAIT – are the core settings we discussed earlier about, ones that control when and how our script will be run.
As you can probably guess, PROTECT attribute controls whether or not we run our script in the bubble (TRUE for running inside, FALSE for running outside) and that WAIT controls synchronous/asynchronous status (TRUE for synchronous, FALSE for asynchronous), but what are the other attributes? Well, glad you asked because I was just about to explain!
Both EVENT and TIMING attributes forms the execution timing part of the scripting: TIMING can be either PRE or POST, and EVENT can be one of the following: STREAM (for package streaming event), LAUNCH (main application launch event) and SHUTDOWN (main application process exiting). By combining these we get our 5 possible script startup times as described earlier. Note that the sixth combination, before shutdown is not a valid setting since App-V cannot predict the time when you are about to start closing the application!
EXTERN is a special attribute introduced in 4.6 version of App-V and can in most cases actually be removed from the list of attributes (just select anywhere in the attribute list, and from context-menu select “Delete EXTERN”). The purpose of EXTERN is to tell to the App-V Client if it should start you script using native version of command interpreter (cmd.exe) when in 64-bit system (when set to TRUE). Non-native meaning 32-bit version of cmd.exe (when set to FALSE). Since this is only meaningful setting for scriptbody –type scripts and only if package is run with x64 version of App-V Client, this attribute should be removed most of the time if not explicitly needed.
Note that the Sequencer does not allow adding those couple of other extra attributes, which had the purpose of controlling the application startup via return value from script. They will be shown in the list if they are in the OSD, but this is one of those places where Sequencer’s knowledge of its own file-format is not really on the level it should be! The attribute names for these attributes are SUCCESSRESULT and ABORTRESULT, and they take numeric value for what to look for as signal for successful execution or reason to stop further processing, respectively.
Note also that Sequencer doesn’t allow adding attribute for custom timeout value (TIMEOUT attribute), which can be used instead of WAIT attribute, but it will preserve it if already present in the OSD file.
Ok, now we have our timings and other conditions set, let’s move on to adding the actual script invocation. This is done by adding yet another element, this time under newly created SCRIPT element we just did:
As you can see, the element names are what we already discussed as script types: HREF for executing single command with optional parameters and SCRIPTBODY for embedding a whole command interpreter script directly in OSD file itself. Depending on the need we now select either one; I’ll take HREF for now:
After adding the script type element, we don’t have any additional attributes or anything to set, just an empty screen. Contents of the script we are adding now needs to be added to textbox on the bottom called “Element Text”:
And that’s it, basically! You have now added new script to be executed on the client when this application is run; after saving the package in the Sequencer of course. And should you need to add more scripts to the same application, you have to add new SCRIPT and HREF/SCRIPTBODY elements one for each as one SCRIPT element cannot have multiple content elements for it.
In closing, let’s take a quick look how the script we just added would look like in the text editor. By using syntax highlighting we can easily see where boundaries for XML elements and attributes are for our OSD file and it is easier to distinguish each SCRIPT element from the rest of the stuff that’s stored in the OSD file:
If we wanted, we could now add those “missing” attributes by hand, like for instance if we know that the script being run returns non-zero value if user profile wasn’t set up properly for the application:
After saving the file, we can re-open the file in Sequencer and see that it indeed shows our added attribute in the list and allows changing it value too:
And that concludes our introduction to App-V OSD scripting; you should now be equipped with basic knowledge of how to go about adding required custom functionality to your virtual applications. Happy scripting!
31.05.2013 at 16:14
Thanks for the detailed info.
I would like to know where to keep the actual vbs file. Do we have to add it inside the package or just we can keep it in the main folder where all the other files like (.osd, sft, sprj) are kept. Please let me know.
Thanks
09.06.2013 at 10:54
If you want to have truly portable package, then it’s best to put VBS inside the package’s internal directory. Of course, if you need to run something outside VE, then this option is not suitable as-is since the script interpreter does not have access to Q: drive when PROTECT=”FALSE”.
You can put the VBS into same directory as physical package files, but then you would need to open up that path through network share to all users that run the package which may not be what you want. Maybe easier overall to store VBS into some common network share from where the users normally get shared files as well (maybe netlogon share?).
/Kalle