Powershell script to run external exe in virtual environment (Updated 5-sept-2013)


I’ve been using vbScript for a long time to write (simple) tools to make life a little easier. Because I’m so at home with vbScript I’ve put off using Powershell for al long time, but since App-V 5.0 uses Powershell extensively there’s no escaping it any more. I have to learn Powershell.

Lately I’ve been busy with running external executables inside an App-V 5 virtual environment (see my previous posts). Doing this requires you to find the packageID en VersionID of an App-V package and type a very long command line. So to set myself al least a bit of a challenge I decided to create my first Powershell script tool to make this a little easier.

What I found out  is that Powershell natively supports the use of Windows Forms allowing you to add an interface to your script. This is a great advantage over vbScript which always required a third party dll of ocx.
To create (and design) an interface I found a great free tool from SAPIEN Technologies called PrimalForms (Community Edition). Which can be downloaded here. The tool is free but you have to register to download.

The goal of my script of my script is to provide an interface that allows you to select an executable and select the virtual environment to run the executable in. Extra features are the option to show the command line used to run the executable using /appvve and to create a user shell extension to allow you to right-click on any executable and select “Run in Virtual Environment” to launch the script.

How the tool works

Run the script using powershell.exe -file <path>\RuninVE.ps1
Make sure that you have configured powershell to allow execution of scripts.

The following dialog will appear:

RuninVE

Select an executable by typing in the textbox or by browsing.
Select one of the available Virtual Environments.

Do one or more of the following:

  • Click “Run” to run the executable in the selected environment.
  • Click “Show CMD” to show the command line used to run the executable.

ShowCmd

  • Click “Enable Shell Ext.” to enable a user shell extension on executable files.
    Now you can right-click on any executable (or SHIFT right-click on a shortcut) and select “Run in Virtual Environment”.

ShellExt

This will automatically launch the script (make sure it is available in the same location, or start manualy and re-enable the shell extension) with the executable already filled in. Now you only have to select a virtual environment and click “Run”.

The source code

The following code is provided “As is” without any form of warranty.

Select it, copy it and save it as RuninVE.ps1. Make sure your execution policy is set correctly and that the App-V module has been loaded at the client.

#######################################################################
# RuninVE.ps1
# Script to Run external executable inside a App-V 5 virtual environment
# V1.0 by Casper van der Kooij, Conclusion FIT
#######################################################################

#Read parameter
param($Exe)

Function Load_lsvVE
#######################################################################
# Function to load the Listview in the form with the available App-V 5
# virtual environments.
#######################################################################
{
	$lsvVE.BeginUpdate() 
	$lsvVE.Items.Clear()
	$statusBar1.text = "Collecting Virtual Environments..."
	$pkgs = Get-AppvClientPackage 
	foreach($pkg in $pkgs) 
	{ 
		If (($pkg.IsPublishedToUser -eq $True) -or ($pkg.IsPublishedGlobally -eq $True))
		{
			$item = $lsvVE.Items.add($pkg.Name)
			$item.Group = $lsvVE.Groups[0]
		}
 	}
	$congroups = Get-AppvClientConnectionGroup 
	foreach($congroup in $congroups)
	{ 
		If (($congroup.IsEnabledToUser -eq $True) -or ($congroup.IsEnabledGlobally -eq $True))
		{
			$item = $lsvVE.Items.add($congroup.Name)
			$item.Group = $lsvVE.Groups[1]
		}

 	}	 
	$lsvVE.EndUpdate()
	$statusBar1.text = "Ready"
} #End Function

Function GetAppvve
#######################################################################
# Function to build the correct /appvve parameter depending on the
# selected virtual environment.
#######################################################################
{
	param([string]$Name, [string]$Type)
	$appvvebase = "/appvve:"
 	If ($Type -eq "Packages")
	{
		$pkg = get-appvclientpackage –name $Name
		$ID = $pkg.PackageID
		$VersionID = $pkg.VersionID
	}
	If ($Type -eq "Connection Groups")
	{
		$ConGroup = get-AppvClientConnectionGroup –name $Name
		$ID = $ConGroup.GroupID
		$VersionID = $ConGroup.VersionID
	}
	Return $appvvebase + $ID + "_" + $VersionID
}

function ChkSelections
#######################################################################
# Function to check user selections
#######################################################################
{
	$txtRunText = $txtRun.text
	$txtRunText = $txtRunText.ToLower()

	If ($lsvVE.SelectedItems.Count -eq 0)
	{
		$Retval = [System.Windows.Forms.MessageBox]::Show("Please select a Virtual Environment.")
		Return $False
	}
	ElseIf (($txtRunText -eq "") -or (!$txtRunText.EndsWith(".exe")) -or (!(Test-Path $txtRunText -PathType Leaf)))
	{
		$Retval = [System.Windows.Forms.MessageBox]::Show("Please enter a valid executable to run.")
		Return $False
	}
	Else
	{
		Return $True
	}
}

#Generated Form Function
function GenerateForm {
########################################################################
# Code Generated By: SAPIEN Technologies PrimalForms (Community Edition) v1.0.10.0
# Generated On: 17-4-2013 8:40
# Generated By: Casper
########################################################################

#region Import the Assemblies
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
#endregion

#region Generated Form Objects
$frmRuninVE = New-Object System.Windows.Forms.Form
$btnToggleShell = New-Object System.Windows.Forms.Button
$btnShowCMD = New-Object System.Windows.Forms.Button
$lsvVE = New-Object System.Windows.Forms.ListView
$lblCredits = New-Object System.Windows.Forms.Label
$btnRun = New-Object System.Windows.Forms.Button
$btnRefresh = New-Object System.Windows.Forms.Button
$btnBrowse = New-Object System.Windows.Forms.Button
$txtRun = New-Object System.Windows.Forms.TextBox
$statusBar1 = New-Object System.Windows.Forms.StatusBar
$lblRun = New-Object System.Windows.Forms.Label
$openFileDialog1 = New-Object System.Windows.Forms.OpenFileDialog
$columnHeader1 = New-Object System.Windows.Forms.ColumnHeader
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
#endregion Generated Form Objects

#----------------------------------------------
#Generated Event Script Blocks
#----------------------------------------------
#Provide Custom Code for events specified in PrimalForms.
$ShowFileDialog= 
{
	$openFileDialog1.ShowDialog()
}

$RunExe= 
{
	If (ChkSelections)
	{
		$appvve = GetAppvve $lsvVE.SelectedItems[0].Text $lsvVE.SelectedItems[0].Group.Header
		Start-Process -FilePath $txtRun.Text -Argumentlist $appvve
	}
}

$handler_label1_Click= 
{
#TODO: Place custom script here
}

$Set_txtRun= 
{
	$txtRun.Text = $openFileDialog1.FileName
}

$handler_statusBar1_PanelClick= 
{
#TODO: Place custom script here
}

$On_Load_frmRuninVE= 
{
	#Set the text for the Shell Ext button to the correct value
	If ($NewShellExtCmd.ToLower() -eq $CurrentShellExtCmd.ToLower())
	{
		$btnToggleShell.text = "Disable Shell Ext."
		#Change button to 'Disable Shell Ext.'
	}
	Else
	{
		$btnToggleShell.text = "Enable Shell Ext."
		#Change button to 'Enable Shell Ext.'
	}

	#Set text in textbox to the passed argument
	$txtRun.text = $Exe

	#Load the list with available Virtual Environments
	Load_lsvVE
}

$ShowCMD= 
{
	If (ChkSelections)
	{
		$appvve = GetAppvve $lsvVE.SelectedItems[0].Text $lsvVE.SelectedItems[0].Group.Header
		$cmd = """" + $txtRun.text + """ " + $appvve
		$RetVal = [System.Windows.Forms.MessageBox]::Show("Command line:`r`n`r`n$cmd`r`n`r`nCopy to Clipboard?", "Command line", 4) 
		If ($RetVal -eq "Yes")
		{
			[Windows.Forms.Clipboard]::SetText($cmd)
		}
	}
}

$handler_textBox1_TextChanged= 
{
#TODO: Place custom script here
}

$handler_label2_Click= 
{
#TODO: Place custom script here
}

$Refresh_lstVE= 
{
	Load_lsvVE
}

$ToggleShellExt= 
#Turn Shell Extension on or off
{
	If ($btnToggleShell.text -eq "Enable Shell Ext.")
	{
		$RegPath = "HKCU:\SOFTWARE\Classes\exefile"
		New-Item -Path $RegPath -type "Directory" –erroraction "silentlycontinue"
		$RegPath = "HKCU:\SOFTWARE\Classes\exefile\shell"
		New-Item -Path $RegPath -type "Directory" –erroraction "silentlycontinue"
		$RegPath = "HKCU:\SOFTWARE\Classes\exefile\shell\RuninVE"
		New-Item -Path $RegPath -type "Directory" –erroraction "silentlycontinue"
		Set-ItemProperty -path $RegPath -name "(default)" -value "Run in Virtual Environment"
		$RegPath = "HKCU:\SOFTWARE\Classes\exefile\shell\RuninVE\command"
		New-Item -Path $RegPath -type "Directory" –erroraction "silentlycontinue"
		Set-ItemProperty -path $RegPath -name "(default)" -value $NewShellExtCmd
		$btnToggleShell.text = "Disable Shell Ext."
	}
	Else
	{
		Remove-Item -Path "HKCU:\SOFTWARE\Classes\exefile\shell\RuninVE" -Recurse
		$btnToggleShell.text = "Enable Shell Ext."
	}
}

$OnLoadForm_StateCorrection=
{#Correct the initial state of the form to prevent the .Net maximized form issue
	$frmRuninVE.WindowState = $InitialFormWindowState
}

#----------------------------------------------
#region Generated Form Code
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 258
$System_Drawing_Size.Width = 597
$frmRuninVE.ClientSize = $System_Drawing_Size
$frmRuninVE.DataBindings.DefaultDataSourceUpdateMode = 0
$frmRuninVE.FormBorderStyle = 3
$frmRuninVE.MaximizeBox = $False
$frmRuninVE.Name = "frmRuninVE"
$frmRuninVE.Text = "Run in App-V 5 Virtual Environment"
$frmRuninVE.add_Load($On_Load_frmRuninVE)

$btnToggleShell.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 510
$System_Drawing_Point.Y = 190
$btnToggleShell.Location = $System_Drawing_Point
$btnToggleShell.Name = "btnToggleShell"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 40
$System_Drawing_Size.Width = 75
$btnToggleShell.Size = $System_Drawing_Size
$btnToggleShell.TabIndex = 13
$btnToggleShell.Text = "Enable Shell Ext."
$btnToggleShell.UseVisualStyleBackColor = $True
$btnToggleShell.add_Click($ToggleShellExt)

$frmRuninVE.Controls.Add($btnToggleShell)

$btnShowCMD.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 510
$System_Drawing_Point.Y = 161
$btnShowCMD.Location = $System_Drawing_Point
$btnShowCMD.Name = "btnShowCMD"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 23
$System_Drawing_Size.Width = 75
$btnShowCMD.Size = $System_Drawing_Size
$btnShowCMD.TabIndex = 12
$btnShowCMD.Text = "Show CMD"
$btnShowCMD.UseVisualStyleBackColor = $True
$btnShowCMD.add_Click($ShowCMD)

$frmRuninVE.Controls.Add($btnShowCMD)

$lsvVE.Columns.Add($columnHeader1)|Out-Null
$lsvVE.DataBindings.DefaultDataSourceUpdateMode = 0
$lsvVE.FullRowSelect = $True

$System_Windows_Forms_ListViewGroup_37 = New-Object System.Windows.Forms.ListViewGroup
$System_Windows_Forms_ListViewGroup_37.Header = "Packages"
$System_Windows_Forms_ListViewGroup_37.Name = "lvgPackages"
$lsvVE.Groups.Add($System_Windows_Forms_ListViewGroup_37)|Out-Null

$System_Windows_Forms_ListViewGroup_38 = New-Object System.Windows.Forms.ListViewGroup
$System_Windows_Forms_ListViewGroup_38.Header = "Connection Groups"
$System_Windows_Forms_ListViewGroup_38.Name = "lvgConGroups"
$lsvVE.Groups.Add($System_Windows_Forms_ListViewGroup_38)|Out-Null

$lsvVE.HideSelection = $False
$lsvVE.HeaderStyle = 1
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 13
$System_Drawing_Point.Y = 64
$lsvVE.Location = $System_Drawing_Point
$lsvVE.MultiSelect = $False
$lsvVE.Name = "lsvVE"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 166
$System_Drawing_Size.Width = 485
$lsvVE.Size = $System_Drawing_Size
$lsvVE.Sorting = 1
$lsvVE.TabIndex = 11
$lsvVE.UseCompatibleStateImageBehavior = $False
$lsvVE.View = 1

$frmRuninVE.Controls.Add($lsvVE)

$lblCredits.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 350
$System_Drawing_Point.Y = 241
$lblCredits.Location = $System_Drawing_Point
$lblCredits.Name = "lblCredits"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 18
$System_Drawing_Size.Width = 275
$lblCredits.Size = $System_Drawing_Size
$lblCredits.TabIndex = 10
$lblCredits.Text = "v1.0, 2013, Casper van der Kooij, Conclusion FIT"
$lblCredits.add_Click($handler_label2_Click)

$frmRuninVE.Controls.Add($lblCredits)

$btnRun.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 510
$System_Drawing_Point.Y = 23
$btnRun.Location = $System_Drawing_Point
$btnRun.Name = "btnRun"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 23
$System_Drawing_Size.Width = 75
$btnRun.Size = $System_Drawing_Size
$btnRun.TabIndex = 6
$btnRun.Text = "Run"
$btnRun.UseVisualStyleBackColor = $True
$btnRun.add_Click($RunExe)

$frmRuninVE.Controls.Add($btnRun)

$btnRefresh.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 510
$System_Drawing_Point.Y = 64
$btnRefresh.Location = $System_Drawing_Point
$btnRefresh.Name = "btnRefresh"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 23
$System_Drawing_Size.Width = 75
$btnRefresh.Size = $System_Drawing_Size
$btnRefresh.TabIndex = 5
$btnRefresh.Text = "Refresh"
$btnRefresh.UseVisualStyleBackColor = $True
$btnRefresh.add_Click($Refresh_lstVE)

$frmRuninVE.Controls.Add($btnRefresh)

$btnBrowse.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 473
$System_Drawing_Point.Y = 23
$btnBrowse.Location = $System_Drawing_Point
$btnBrowse.Name = "btnBrowse"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 23
$System_Drawing_Size.Width = 25
$btnBrowse.Size = $System_Drawing_Size
$btnBrowse.TabIndex = 3
$btnBrowse.Text = "..."
$btnBrowse.UseVisualStyleBackColor = $True
$btnBrowse.add_Click($ShowFileDialog)

$frmRuninVE.Controls.Add($btnBrowse)

$txtRun.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 13
$System_Drawing_Point.Y = 26
$txtRun.Location = $System_Drawing_Point
$txtRun.Name = "txtRun"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 20
$System_Drawing_Size.Width = 460
$txtRun.Size = $System_Drawing_Size
$txtRun.TabIndex = 2
$txtRun.add_TextChanged($handler_textBox1_TextChanged)

$frmRuninVE.Controls.Add($txtRun)

$statusBar1.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 0
$System_Drawing_Point.Y = 236
$statusBar1.Location = $System_Drawing_Point
$statusBar1.Name = "statusBar1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 22
$System_Drawing_Size.Width = 597
$statusBar1.Size = $System_Drawing_Size
$statusBar1.SizingGrip = $False
$statusBar1.TabIndex = 0
$statusBar1.Text = "Ready"
$statusBar1.add_PanelClick($handler_statusBar1_PanelClick)

$frmRuninVE.Controls.Add($statusBar1)

$lblRun.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 13
$System_Drawing_Point.Y = 10
$lblRun.Location = $System_Drawing_Point
$lblRun.Name = "lblRun"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 23
$System_Drawing_Size.Width = 100
$lblRun.Size = $System_Drawing_Size
$lblRun.TabIndex = 7
$lblRun.Text = "Executable to run:"
$lblRun.add_Click($handler_label1_Click)

$frmRuninVE.Controls.Add($lblRun)

$openFileDialog1.Filter = "Executables (*.exe)|*.exe"
$openFileDialog1.ShowHelp = $True
$openFileDialog1.Title = "Select executable to run..."
$openFileDialog1.add_FileOk($Set_txtRun)

$columnHeader1.Text = "Virtual Environment"
$columnHeader1.Width = 481

#endregion Generated Form Code

#Enable VisualStyles to show groups in listview
[void][System.Windows.Forms.Application]::EnableVisualStyles()
#Save the initial state of the form
$InitialFormWindowState = $frmRuninVE.WindowState
#Init the OnLoad event to correct the initial state of the form
$frmRuninVE.add_Load($OnLoadForm_StateCorrection)
#Show the Form
$frmRuninVE.ShowDialog()| Out-Null

} #End Function

#Get the Full Script path inclusing filename
$FullScriptPath = $MyInvocation.MyCommand.Definition

#Get the path to the powershell executable
$PSPathRegKey = "HKLM:\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine"
$PSExe = Join-Path -path (Get-ItemProperty -path $PSPathRegKey)."ApplicationBase" -ChildPath "powershell.exe"

#Get the current command line for the shell extension from the registry
$ShellExtRegKey = "HKCU:\SOFTWARE\Classes\exefile\shell\RuninVE\command"
If (Test-Path $ShellExtRegKey)
{
	$CurrentShellExtCmd = (Get-ItemProperty -path $ShellExtRegKey)."(Default)"
}
Else
{
	$CurrentShellExtCmd = ""
}

#Make the new commandline for the Shell Extension
$NewShellExtCmd = """" + $PSExe + """ " + "-WindowStyle Hidden -File """ + $FullScriptPath + """ ""%1"""	
Write-Host $CurrentShellExtCmd
Write-Host $NewShellExtCmd

#Call the GeneratForm Function to start the app
GenerateForm
– Update 5-sept-2013 –

Below I ‘ve added a commandline version of the script. This can be used to create a shortcut to start an exe in a VE by name or ID of the VE. No need to know the Version ID.

Copy the code below and save it as RuninVEcmd.ps1. Make sure your execution policy is set correctly and that the App-V module has been loaded at the client.

#######################################################################################
# RuninVEcmd.ps1
# Script to Run external executable inside a App-V 5 Virtual Environment (VE) via the 
# commandline.
# V1.0 by Casper van der Kooij, Conclusion FIT
# Syntax:
# powershell.exe [-WindowStyle Hidden] -file <Path>\RuninVEcmd.ps1 -exe <TargetExe> -id <Name/ID> [-type <VEType>]
# TargetExe = The full path to the executable to be run.
# Name/ID   = The Name or ID of the VE that the executable has to be run in.
# VEType    = The type of VE. Valid values are: pkg - Single package
#                                               cg  - Connection Group
#             If omitted pkg is assumed.
#######################################################################################

#Read parameter
param([String]$Exe, [String]$ID, [String]$Type="pkg")
If (!$Exe)
{
    "Please supply an exe to run using the -exe parameter"
    Break
} 
If (!$ID)
{
    "Please supply an ID (Name or GUID) of package or ConnectionGroup using the -id parameter"
    Break
} 

$Appvvebase = "/appvve:"

#Check if the ID is a GUID or not. No I did not come up with this my self. I found it on the internet :-)
$IDisGUID = ($ID -match("^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$"))

If ($Type -eq "pkg")
{
    If ($IDisGUID)
    {
        $Package = Get-AppvClientPackage –PackageID $ID
    }
    Else
    {
	    $Package = Get-AppvClientPackage –name $ID
    }
	If (!$Package)
	{
		"App-V package with specified ID not found."
		Break
	}

	$ID = $Package.PackageID
	$VersionID = $Package.VersionID	
}
ElseIf ($Type -eq "cg")
{
	If ($IDisGUID)
    {
        $Group = Get-AppvClientConnectionGroup –GroupID $ID
    }
    Else
    {
	$Group = Get-AppvClientConnectionGroup –name $ID
    }
	If (!$Group)
	{
		"App-V Connection Group with specified ID not found."
		Break
	}
	$ID = $Group.GroupID
	$VersionID = $Group.VersionID	
}
Else
{
    "Invalid Type specified."
    Break
}
$Appvve = $Appvvebase + $ID + "_" + $VersionID
Start-Process -FilePath $Exe -Argumentlist $Appvve
Advertisements

Tags: , , , , , , ,

About Casper van der kooij

Technical Lead Application Packaging @ Conclusion Future Infrastructure Technologies, Utrecht, The Netherlands.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: