Monday, January 20, 2025
Azure Virtual Desktop

AVD App Deployments Made Easy with Nerdio ShellApps and PSADT

Hello! In this post, we’ll explore how to integrate Nerdio ShellApps with the PowerShell App Deployment Toolkit (PSADT) to streamline even the most complex application deployments. This powerful combination is particularly useful for handling challenging apps that require custom registry entries or files distributed across all user profiles. By pairing PSADT with Nerdio ShellApps, you can simplify these processes and take control of your deployments.

Additionally, you can reuse your existing PSADT scripts from platforms like Intune or ConfigMgr, creating a unified, versatile application deployment framework for your environment. For an added bonus, by also using the Evergreen PowerShell module, you can automatically download and install the latest version of your applications, something I’ll cover below in this post. One framework to rule them all!

Provision and Integrate Storage Account

For Nerdio to deploy ShellApps, you first need to create a storage account within Azure and integrate into NME (I won’t show you how to deploy a storage account as it’s simple enough), once you have your storage account, within NME, go to Nerdio Environment, under Unified Application Management, select link:

then select your existing or create new storage account, with the following options, like so:

Now you’re ready!

Create your PSADT files

As I’m sure you’re familiar (as you wouldn’t be here), create your application deployment scripts using the PSADT framework, if you’re unfamiliar then I’ve got a blog post on a few PSADT examples, see here (there’s also a github repo in this post for real world examples):

Create your ShellApp

For this example, I will be using a PSADT script to deploy Visual Studio Code, I know we can use the Public WinGet Repo for this within Nerdio but this is what I am choosing to use as an example, your PSADT deployment can be anything! All of the code \ source files \ screenshots for this example can be found on my AVD GitHub repo here:

To create the ShellApp, go to Applications, ShellApps within NME and click Add:

Name \ Description

Give the application a good name and description, like so:

  • Name: Microsoft Visual Studio Code
  • Description: Script that uses the PowerShell Evergreen mode to download and install the latest version of VSCode.

File

Upload your zipped up (important to zip) PSADT file and make sure that the root folder in the zipped up directly contains Deploy-application.exe (do not create more additional subfolders), do not select unzip as I have taken care of expanding the archive in the installation script, your file section should be like so:

Detect

Just like Intune \ ConfigMgr, we need to give Nerdio a script that determines if the application is present or not. Use the following as a framework (also in my GitHub link above):

<#
.SYNOPSIS
    Customised Win32App Detection Script
.DESCRIPTION
    This script identifies if a specific software, defined by its display name, is installed on the system.
    It checks the uninstall keys in the registry and reports back.
.EXAMPLE
    $TargetSoftware = 'Google Chrome'  # Searches for an uninstall key with the display name 'Google Chrome'
#>

# Define the name of the software to search for
$TargetSoftware = 'Microsoft Visual Studio Code'

# Function to fetch uninstall keys from the registry
function Fetch-UninstallKeys {
    [CmdletBinding()]
    param (
        [string]$TargetName
    )

    # Continue on error
    $ErrorActionPreference = 'Continue'

    # Define uninstall registry paths
    $registryPaths = @(
        "registry::HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall",
        "registry::HKLM\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
    )

    # Initialize software list
    $softwareList = @()

    # Loop through each registry path to find software
    foreach ($path in $registryPaths) {
        $softwareList += Get-ChildItem $path | Get-ItemProperty | Where-Object { $_.DisplayName } | Sort-Object DisplayName
    }

    # Filter software list based on target name
    if ($TargetName) {
        $softwareList | Where-Object { $_.DisplayName -like "*$TargetName*" }
    } else {
        $softwareList | Sort-Object DisplayName -Unique
    }
}

# Main script logic
$Context.Log("Starting detection for: $TargetSoftware")

# Fetch the list of detected software
$DetectedSoftware = Fetch-UninstallKeys -TargetName $TargetSoftware

# Check if the target software is installed
if ($DetectedSoftware) {
    $Context.Log("$TargetSoftware is installed.")
    return $true
} else {
    $Context.Log("$TargetSoftware is NOT installed.")
    return $false
}
PowerShell

Install

Now we need to have our installation script, this script will download the .zip from the storage account, unzip it, install with various log functions.

# Start Log
$Context.Log("Starting installation...")

# Get the installer path from Nerdio
try {
    $installerPath = $Context.GetAttachedBinary()
    $Context.Log("Installer binary downloaded successfully. Path: $installerPath")
}
catch {
    $Context.Log("Error downloading installer binary: " + $_.Exception.Message)
    throw
}

# Define temporary working directories
$tempDir = "C:\AppTemp"

# Ensure temporary directory exists
try {
    if (-not (Test-Path $tempDir)) {
        New-Item -Path $tempDir -ItemType Directory -ErrorAction Stop | Out-Null
        $Context.Log("Temporary directory created: $tempDir")
    } else {
        $Context.Log("Temporary directory already exists: $tempDir")
    }
}
catch {
    $Context.Log("Error creating or verifying temporary directory: " + $_.Exception.Message)
    throw
}

# Unzip the file to the temporary directory
try {
    $Context.Log("Extracting zip file to temporary directory...")
    Expand-Archive -LiteralPath $installerPath -DestinationPath $tempDir -Force
    $Context.Log("Zip file extracted successfully to: $tempDir")
}
catch {
    $Context.Log("Error during zip file extraction: " + $_.Exception.Message)
    throw
}

# Append the Deploy-Application.exe to the installer path
try {
    $Context.Log("Locating Deploy-Application.exe...")
    $deployExecutable = Join-Path -Path $tempDir -ChildPath "Deploy-Application.exe"
    $Context.Log("Executable path set to: $deployExecutable")
}
catch {
    $Context.Log("Error constructing executable path: " + $_.Exception.Message)
    throw
}

# Install application
try {
    $Context.Log("Preparing to execute installer...")
    $commandToExecute = "`"$deployExecutable`" -DeploymentType install"
    $Context.Log("Command to execute: $commandToExecute")
    
    Start-Process -FilePath "`"$deployExecutable`"" -ArgumentList "-DeploymentType install" -Wait -ErrorAction Stop
    
    $Context.Log("Installation command executed successfully.")
    $Context.Log("Installation completed successfully.")
}
catch {
    $Context.Log("Error during installation execution: " + $_.Exception.Message)
    throw
}

# Cleanup temporary files
try {
    $Context.Log("Cleaning up temporary files...")
    Remove-Item -Path $tempDir -Force -Recurse -ErrorAction SilentlyContinue
    $Context.Log("Temporary files cleaned up successfully.")
}
catch {
    $Context.Log("Error during cleanup: " + $_.Exception.Message)
}
PowerShell

Uninstall

You may want to use Nerdio to uninstall software, so I always recommend investing the time to ensure your uninstall PSADT scripts work just as well as your installation scripts, the great thing about PSADT is that it’s pretty much the same script just with uninstall instead of install under DeploymenType, here’s the VSCode example:

# Start Log
$Context.Log("Starting uninstallation...")

# Get the installer path from Nerdio
try {
    $installerPath = $Context.GetAttachedBinary()
    $Context.Log("Installer binary downloaded successfully. Path: $installerPath")
}
catch {
    $Context.Log("Error downloading installer binary: " + $_.Exception.Message)
    throw
}

# Define temporary working directories
$tempDir = "C:\AppTemp"

# Ensure temporary directory exists
try {
    if (-not (Test-Path $tempDir)) {
        New-Item -Path $tempDir -ItemType Directory -ErrorAction Stop | Out-Null
        $Context.Log("Temporary directory created: $tempDir")
    } else {
        $Context.Log("Temporary directory already exists: $tempDir")
    }
}
catch {
    $Context.Log("Error creating or verifying temporary directory: " + $_.Exception.Message)
    throw
}

# Unzip the file to the temporary directory
try {
    $Context.Log("Extracting zip file to temporary directory...")
    Expand-Archive -LiteralPath $installerPath -DestinationPath $tempDir -Force
    $Context.Log("Zip file extracted successfully to: $tempDir")
}
catch {
    $Context.Log("Error during zip file extraction: " + $_.Exception.Message)
    throw
}

# Append the Deploy-Application.exe to the installer path
try {
    $Context.Log("Locating Deploy-Application.exe...")
    $deployExecutable = Join-Path -Path $tempDir -ChildPath "Deploy-Application.exe"
    $Context.Log("Executable path set to: $deployExecutable")
}
catch {
    $Context.Log("Error constructing executable path: " + $_.Exception.Message)
    throw
}

# Uninstall application
try {
    $Context.Log("Preparing to execute uninstaller...")
    $commandToExecute = "`"$deployExecutable`" -DeploymentType uninstall"
    $Context.Log("Command to execute: $commandToExecute")
    
    Start-Process -FilePath "`"$deployExecutable`"" -ArgumentList "-DeploymentType uninstall" -Wait -ErrorAction Stop
    
    $Context.Log("Uninstallation command executed successfully.")
    $Context.Log("Uninstallation completed successfully.")
}
catch {
    $Context.Log("Error during uninstallation execution: " + $_.Exception.Message)
    throw
}

# Cleanup temporary files
try {
    $Context.Log("Cleaning up temporary files...")
    Remove-Item -Path $tempDir -Force -Recurse -ErrorAction SilentlyContinue
    $Context.Log("Temporary files cleaned up successfully.")
}
catch {
    $Context.Log("Error during cleanup: " + $_.Exception.Message)
}
PowerShell

That’s it your ready to deploy your app on your image \ host pools.

Trust but Verify

There’s multiple ways to deploy applications within Nerdio, but I will quickly show you how to add it to your desktop image, within NME, go to Desktop Images, find your image and select Deploy Apps:

Wait for Nerdio to do it’s thing, it should take approx 5-10 minutes, view the task log and all should be well, for the output view here:

Within the results, we can see the VSCode was successfully detected:

Happy Nerdioing!

One thought on “AVD App Deployments Made Easy with Nerdio ShellApps and PSADT

Leave a Reply...