Unified Application Management: Shell Apps Overview and Usage
The versatile Shell Apps feature offers the ability to create complex scripted deployment for Unified Application Management (UAM) deployment. Administrators can deploy multiple components via PowerShell and use the detection script feature to report success or failure information back to the UAM policy console for review.
This feature is intended for use with large or complex applications, or where deployment via Winget is not practical or desirable. Because the feature makes use of native PowerShell scripts, Winget can be called as part of an installation script if required.
Applications deployed using the Shell Apps feature are deployed exclusively in the device system context, avoiding the need for any service account.
This feature functions in a similar way to Nerdio Manager Scripted actions. If you are making use of scripted actions, there should be no complications when adopting the Shell Apps feature.
Prerequisites
PowerShell remoting (WSMan) must be enabled for the target devices.
Limitations
Shell Apps cannot be assigned to Intune devices if it contains secure variables.
Because this is a preview feature, the items noted below are not currently available. These are planned for future releases.
Secure variable support: As with scripted actions, secure variables provide the ability to deliver obfuscated, predefined items into the script.
SCCM integration: The ability to ingest scripted installations with their associated binaries from connected SCCM environments.
Getting Started
In Nerdio Manager, navigate to Applications > Shell Apps to work with the Shell Apps feature. See Unified Application Management: Manage Shell Apps for details.
Notepad ++ (.EXE) and 7-Zip (MSI) are included when you get started.
Deployments are split into the following script areas: Detection, Install and Uninstall. All components are required to form a valid application package.
Each application package contains a switch labeled Public. This controls the application’s visibility within the UAM unified catalog page. It is recommended that this switch remains Off until you have completed your scripts, to avoid any potential deployment issues.
Please refer to the guidance below to understand how these example scripts function, and how you can reuse these to deploy your own applications using the Shell Apps feature.
File Packages
Shell Apps supports the upload of package files if required. This is useful if you want to include your installer files, and do not want your install routine to download files from an internet location. All application types supported by UAM are supported for Shell App installations.
Usage
If you are using an uploaded installer package, this can be called as part of the script by using the built in variable $Context.GetAttachedBinary(). For example, if you want to install an attached installer package, you may use the command:
Start-Process "$Context.GetAttachedBinary()" -Wait -ArgumentList "/S"
ZIP Archive Support
From v6.2 of Nerdio Manager, Shell Apps now supports the use of .ZIP archives to deploy a group of files to a desktop as part of the install process. Optionally, these .ZIP archives may be automatically extracted for application deployment tasks.
Usage
When importing a .ZIP archive on the Shell Apps ‘Files’ page, the additional ‘Unzip’ option is available. If selected, the script automatically extracts the compressed files to a temporary folder in C:\Windows\Temp on the target desktop.
After extraction, the script’s context is set to the created temporary directory automatically, therefore all installation tasks can be completed using relative paths in the script. For example, to call the file EXAMPLE.MSI from the Shell Apps installation script, simply use the command .\EXAMPLE.MSI as part of your install process. Arguments can be added as required. Additionally, other files and tasks, such as transforms, may also be called from the relative path.
Example: Notepad ++ (.EXE)
Detection Script
The detection script function provides the ability to create a ruleset that returns a positive or negative value for a specific query. We provide an example below of the detection script for a specific application (Notepad ++).
In this example, the PowerShell script efficiently checks for the existence of a specific program by verifying the existence of its installation folder and the program executable file within that folder.
Script Body
$basePath = $env:ProgramFiles
$programFolderName = "Notepad++"
$programFolderFullPath = Join-Path $basePath $programFolderName
$programFolderExists = Test-Path $programFolderFullPath
if (!$programFolderExists) {
return $false
}
$programFileName = 'notepad++.exe'
$programFileFullPath = Join-Path $programFolderFullPath $programFileName
return Test-Path $programFileFullPath
Components Breakdown
-
Variables:
$basePath: Stores the path of the Program Files directory retrieved from the system environment variables ($env:ProgramFiles).
$programFolderName: Specifies the name of the program folder (in this case, "Notepad++").
$programFolderFullPath: Combines $basePath and $programFolderName using Join-Path to get the full path of the program folder.
-
Checking Program Folder Existence:
$programFolderExists = Test-Path $programFolderFullPath if (!$programFolderExists) { return $false }
Test-Path: Cmdlet used to determine whether a file or directory exists at a specified path.
$programFolderExists: Stores the result of the existence check for the program folder.
If the program folder doesn't exist (!$programFolderExists), the script returns $false, indicating that the program is not installed.
-
Constructing Program File Path:
$programFileName = 'notepad++.exe' $programFileFullPath = Join-Path $programFolderFullPath $programFileName
$programFileName: Specifies the name of the program executable file.
$programFileFullPath: Combines $programFolderFullPath and $programFileName using Join-Path to get the full path of the program executable file.
-
Checking Program File Existence:
return Test-Path $programFileFullPath
Checks if the program executable file exists at the specified path.
The script returns the result of this existence check, which indicates whether the program is installed ($true) or not ($false).
Installation Script
The installation script installs and configures all required components for the successful deployment of the application.
In this example, the PowerShell script automates the installation of Notepad++ by downloading the installer, executing it silently (/S argument), and cleaning up afterward.
Script Body
$basePath = $env:TEMP
$installerFolderName = "Notepad $(New-Guid)"
$installerFolderFullPath = Join-Path $basePath $installerFolderName
$installerFolderExists = Test-Path $installerFolderFullPath
if ($installerFolderExists) {
Remove-Item $installerFolderFullPath -Recurse -Force -ErrorAction Stop
}
New-Item -Path $basePath -Name $installerFolderName -ItemType Directory -Force | Out-Null
$installerName = 'installer.exe'
$installerFullPath = Join-Path $installerFolderFullPath $installerName
$installerUrl = "https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8.6/npp.8.6.Installer.x64.exe"
Invoke-WebRequest -URI $installerUrl -OutFile $installerFullPath
Start-Process "$installerFullPath" -Wait -ArgumentList "/S"
Remove-Item $installerFolderFullPath -Recurse -Force
$Context.Log('Install script completed')
Components Breakdown
-
Variables:
$basePath: Stores the path of the temporary directory retrieved from the system environment variables ($env:TEMP).
$installerFolderName: Specifies the name of the folder for the Notepad++ installer. It includes a dynamically-generated GUID to ensure uniqueness.
$installerFolderFullPath: Combines $basePath and $installerFolderName using Join-Path to get the full path of the installer folder.
-
Checking and Creating Installer Folder:
$installerFolderExists = Test-Path $installerFolderFullPath if ($installerFolderExists) { Remove-Item $installerFolderFullPath -Recurse -Force -ErrorAction Stop } New-Item -Path $basePath -Name $installerFolderName -ItemType Directory -Force | Out-Null
Checks if the installer folder already exists. If it does, it removes it recursively and forcefully to ensure a clean installation environment.
Creates a new directory for the installer using New-Item cmdlet.
-
Downloading Installer:
$installerUrl = "https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8.6/npp.8.6.Installer.x64.exe" Invoke-WebRequest -URI $installerUrl -OutFile $installerFullPath
Specifies the URL of the Notepad++ installer.
Downloads the installer executable using Invoke-WebRequest and saves it to the specified path.
-
Starting Installation Process:
Start-Process "$installerFullPath" -Wait -ArgumentList "/S"
Initiates the installation process by starting the installer executable. The -Wait parameter ensures that the script waits for the installation to complete before proceeding.
-
Cleaning Up:
Remove-Item $installerFolderFullPath -Recurse -Force
Removes the installer folder and its contents after the installation is complete, ensuring a clean environment.
-
Logging:
$Context.Log('Install script completed')
Logs a message indicating that the installation script has completed. This function can also log a message which is later available on the Policy Details page. This can be used as $Context.Log(string $message).
Uninstall Script
The uninstall script provides the ability to remove a specified application as part of the policy assessment process. The detection script is re-used to confirm the specified detection items are not present.
In this example, the PowerShell script automates the uninstallation of Notepad++ by checking for the existence of both the program folder and the uninstaller executable. If both are found, it executes the uninstaller silently (/S argument).
Script Body
$basePath = $env:ProgramFiles
$programFolderName = "Notepad++"
$programFolderFullPath = Join-Path $basePath $programFolderName
$programFolderExists = Test-Path $programFolderFullPath
if (!$programFolderExists) {
throw "Notepad++ is not found"
}
$uninstallerFileName = 'uninstall.exe'
$uninstallerFileFullPath = Join-Path $programFolderFullPath $uninstallerFileName
$uninstallerFileExists = Test-Path $uninstallerFileFullPath
if (!$uninstallerFileExists) {
throw "Notepad++ uninstaller is not found"
}
Start-Process "$uninstallerFileFullPath" -Wait -ArgumentList "/S"
$Context.Log('Uninstall script completed')
Components Breakdown
-
Variables:
$basePath: Stores the path of the Program Files directory retrieved from the system environment variables ($env:ProgramFiles).
$programFolderName: Specifies the name of the program folder (in this case, "Notepad++").
$programFolderFullPath: Combines $basePath and $programFolderName using Join-Path to get the full path of the program folder.
-
Checking Program Folder Existence:
$programFolderExists = Test-Path $programFolderFullPath if (!$programFolderExists) { throw "Notepad++ is not found" }
Test-Path: Cmdlet used to determine whether a file or directory exists at a specified path.
Throws an error if the Notepad++ program folder doesn't exist, indicating that Notepad++ is not installed.
-
Checking Uninstaller File Existence:
$uninstallerFileName = 'uninstall.exe' $uninstallerFileFullPath = Join-Path $programFolderFullPath $uninstallerFileName $uninstallerFileExists = Test-Path $uninstallerFileFullPath if (!$uninstallerFileExists) { throw "Notepad++ uninstaller is not found" }
Constructs the full path to the Notepad++ uninstaller executable (uninstall.exe).
Checks if the uninstaller executable exists. If it doesn't, throws an error indicating that the uninstaller is not found.
-
Starting Uninstallation Process:
Start-Process "$uninstallerFileFullPath" -Wait -ArgumentList "/S"
Initiates the uninstallation process by starting the uninstaller executable. The -Wait parameter ensures that the script waits for the uninstallation to complete before proceeding.
-
Logging:
$Context.Log('Uninstall script completed')
Logs a message indicating that the uninstallation script has completed. This function can also log a message which is later available on the Policy Details page. This can be used as $Context.Log(string $message).
Example: 7-Zip (.MSI)
Detection Script
The detection script function provides the ability to create a ruleset which returns a positive or negative value for a specific query. We provide an example below of the detection script for a specific application (7-Zip). This PowerShell script checks for the existence of a specific program by verifying the presence of its uninstall information in the Windows Registry.
Script Body
$basePath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
$productCode = '{23170F69-40C1-2702-2201-000001000000}'
$programRegistryPath = Join-Path $basePath $productCode
return Test-Path $programRegistryPath
Components Breakdown
-
Variables:
$basePath: Stores the base path in the Windows Registry where the uninstall information for installed programs is typically stored. In this case, it points to HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, which is the registry location for installed programs for all users.
$productCode: Specifies the unique product code associated with the program being checked. This code is typically a GUID (Globally Unique Identifier) and is used to identify the program in the registry.
$programRegistryPath: Combines the base path ($basePath) and the product code ($productCode) using Join-Path to form the full registry path to the program's uninstall information.
-
Checking Program Registry Key Existence:
return Test-Path $programRegistryPath
Test-Path: Cmdlet used to determine whether a file, directory, or in this case, a registry key exists at a specified path.
The script returns the result of this existence check, which indicates whether the program is installed (registry key exists) or not.
Installation Script
The installation script installs and configures all required components for the successful deployment of the application. This PowerShell script automates the installation of 7-Zip by downloading the installer MSI file, executing it silently (/qn argument), and cleaning up afterward.
Script Body
$basePath = $env:TEMP
$installerFolderName = "7zip $(New-Guid)"
$installerFolderFullPath = Join-Path $basePath $installerFolderName
$installerFolderExists = Test-Path $installerFolderFullPath
if ($installerFolderExists) {
Remove-Item $installerFolderFullPath -Recurse -Force -ErrorAction Stop
}
New-Item -Path $basePath -Name $installerFolderName -ItemType Directory -Force | Out-Null
$installerName = 'installer.msi'
$installerFullPath = Join-Path $installerFolderFullPath $installerName
$installerUrl = "https://7-zip.org/a/7z2201-x64.msi"
Invoke-WebRequest -URI $installerUrl -OutFile $installerFullPath
Start-Process msiexec -ArgumentList "/i `"$installerFullPath`" /qn" -Wait
Remove-Item $installerFolderFullPath -Recurse -Force
$Context.Log('Install script completed')
Components Breakdown
-
Variables:
$basePath: Stores the path of the temporary directory retrieved from the system environment variables ($env:TEMP).
$installerFolderName: Specifies the name of the folder for the 7-Zip installer. It includes a dynamically generated GUID to ensure uniqueness.
$installerFolderFullPath: Combines $basePath and $installerFolderName using Join-Path to get the full path of the installer folder.
-
Checking and Creating Installer Folder:
$installerFolderExists = Test-Path $installerFolderFullPath if ($installerFolderExists) { Remove-Item $installerFolderFullPath -Recurse -Force -ErrorAction Stop } New-Item -Path $basePath -Name $installerFolderName -ItemType Directory -Force | Out-Null
Checks if the installer folder already exists. If it does, it removes it recursively and forcefully to ensure a clean installation environment.
Creates a new directory for the installer using New-Item cmdlet.
-
Downloading Installer:
$installerName = 'installer.msi' $installerFullPath = Join-Path $installerFolderFullPath $installerName $installerUrl = "https://7-zip.org/a/7z2201-x64.msi" Invoke-WebRequest -URI $installerUrl -OutFile $installerFullPath
Specifies the URL of the 7-Zip installer.
Downloads the installer MSI file using Invoke-WebRequest and saves it to the specified path.
-
Starting Installation Process:
Start-Process msiexec -ArgumentList "/i `"$installerFullPath`" /qn" -Wait
Initiates the installation process by starting the msiexec utility with the /i (install) flag and the path to the MSI installer file ($installerFullPath). The /qn parameter installs silently without displaying any user interface.
-
Cleaning Up:
Remove-Item $installerFolderFullPath -Recurse -Force
Removes the installer folder and its contents after the installation is complete, ensuring a clean environment.
-
Logging:
$Context.Log('Install script completed')
Logs a message indicating that the installation script has completed. This function can also log a message which is later available on policy Details page. This can be used as $Context.Log(string $message).
Uninstall Script
The uninstall script provides the ability to remove a specified application as part of the policy assessment process. The detection script can be re-used to confirm the specified detection items are not present. This example PowerShell script automates the uninstallation of 7-Zip by checking for the existence of its uninstallation registry key and initiating the uninstallation process silently.
Script Body
$basePath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
$productCode = '{23170F69-40C1-2702-2201-000001000000}'
$programRegistryPath = Join-Path $basePath $productCode
$programExists = Test-Path $programRegistryPath
if (!$programExists) {
throw "7zip is not found"
}
Start-Process msiexec -ArgumentList "/x `"$productCode`" /qn" -Wait
$Context.Log('Uninstall script completed')
Components Breakdown
-
Variables:
$basePath: Stores the base path in the Windows Registry where uninstall information for installed programs is typically stored. In this case, it points to HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, which is the registry location for installed programs for all users.
$productCode: Specifies the unique product code associated with 7-Zip. This code is typically a GUID (Globally Unique Identifier) and is used to identify the program in the registry.
$programRegistryPath: Combines the base path ($basePath) and the product code ($productCode) using Join-Path to form the full registry path to the program's uninstall information.
-
Checking Program Registry Key Existence:
$programExists = Test-Path $programRegistryPath if (!$programExists) { throw "7zip is not found" }
Test-Path: Cmdlet used to determine whether a registry key exists at a specified path.
Throws an error if the registry key for 7-Zip doesn't exist, indicating that 7-Zip is not installed.
-
Starting Uninstallation Process:
Start-Process msiexec -ArgumentList "/x `"$productCode`" /qn" -Wait
Initiates the uninstallation process by starting the msiexec utility with the /x (uninstall) flag and the product code ($productCode). The /qn parameter uninstalls silently without displaying any user interface.
-
Logging:
$Context.Log('Uninstall script completed')
Logs a message indicating that the uninstallation script has completed. This function can also log a message which is later available on the Policy Details page. This can be used as $Context.Log(string $message).
Versioning
As of Nerdio Manager v6.6, Shell Apps now supports versioning. This section provides guidance for creating and using application versions within the user interface.
Version Configuration UI and Flow Examples
See Unified Application Management: Shell Apps Technical Reference Guide for details about the version configuration UI and flow examples.
Versioning Example
The example below is provided as a suggested approach to managing versions within Shell Apps. This is a very flexible feature, and customers may make use of a range of different detected environmental conditions to determine the installation status of a package. In this example, we use file version as our detection rule, however this function is equally applicable to registry keys, files, and folders.
Detection Script
The detection script below interrogates the executable file at the path specified and returns the Product Version information.
Note: The detection script must be configured to return a result in the version format x.x.x.x. For a given version, the version Name in the version properties must be identical to the value returned by the detection script for any associated polices to assess the application's status.
$nppDirPath = Join-Path $env:ProgramFiles "Notepad++"
$exePath = Join-Path $nppDirPath 'notepad++.exe'
if (!(Test-Path $exePath)) {
return $null # nothing is installed
}
# picking version from .exe metadata
return (Get-Item $exePath).VersionInfo.ProductVersion
Installation Script
The installation script below installs the target version of the application, which is defined in the UAM policy and available in the script as $Context.TargetVersion.
$installerTempDir = Join-Path $env:TEMP "Notepad $(New-Guid)"
$installerPath = Join-Path $installerTempDir 'installer.exe'
# creating directory in Temp folder
New-Item -Path $installerTempDir -ItemType Directory -Force | Out-Null
# generating url dynamically based on target version
# configured in deployment policy
$version = $Context.TargetVersion
$installerUrl = "https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v$version/npp.$version.Installer.x64.exe"
# downloading the file and running install with silent switch
Invoke-WebRequest -URI $installerUrl -OutFile $installerPath
Start-Process "$installerPath" -Wait -ArgumentList "/S"
# cleaning up after ourselves
Remove-Item $installerTempDir -Recurse -Force
Uninstall Script
The uninstall script below simply removes the application using the routine defined if the detection rule conditions are met.
$nppDirPath = Join-Path $env:ProgramFiles "Notepad++"
$uninstallerPath = Join-Path $nppDirPath 'uninstall.exe'
Start-Process "$uninstallerPath" -Wait -ArgumentList "/S"
Comments (0 comments)