Public/New-APFConfigDeployment.ps1

function New-APFConfigDeployment {
    <#
    .SYNOPSIS
        Creates an APF (Application Packaging Framework) configuration deployment package for Intune.

    .DESCRIPTION
        The New-APFConfigDeployment function creates a folder named after the deployment in
        DestinationFolder, copies the matching APF installer templates into it and fills in
        config.installer.json and the detection script. Optionally it also wraps the folder into an
        Intune package ("<Name>.intunewin" in DestinationFolder).

        The parameters available depend on ConfigurationType (they are dynamic parameters):
          - All types: Name, Version, DestinationFolder, CreateIntuneWinPackage.
          - Registry: IncludedFiles, RegistryValue, Target.
          - PowerShellProfiles and Files: Files, FilesDirectoryName.
          - Script-OS and WindowsFeature: IncludedFiles.
          - StandAlone-Exe: Path, IncludedFiles, CLIApp.
          - Script-App and Script-User: Path (the deployment script), IncludedFiles.
          - Custom: IncludedFiles, Target.
          - Standalone-Application: Path, IncludedFiles, LauncherName, LauncherRelativePath.

        Dynamic parameters:
          Name The name of the deployment; written into the configuration files and
                                  used as the folder name.
          Version The version of the deployment (x.x.x.x).
          Path The main file (StandAlone-Exe), the file or folder of a standalone
                                  application (Standalone-Application) or the PowerShell deployment
                                  script (Script-App, Script-User).
          DestinationFolder Where the package folder is created. Default is the current directory.
          CreateIntuneWinPackage Also create a .intunewin package.
          IncludedFiles Additional files or folders to include in the package.
          RegistryValue One registry entry in the format of the registry CSV columns:
                                  "RegistryPath,KeyName,ValueName,ValueType,ValueData,State", for example
                                  "HKLM:\Software,MySoftware,MyValue,String,MyData,ADD". State is ADD,
                                  MODIFY or REMOVE. More entries can be added to the generated
                                  "<Name>_Registry.csv" file. Any failed entry fails the whole deployment.
          Target 'System' (default) or 'User'.
          LauncherName The file that launches a standalone application.
          LauncherRelativePath The relative path of the launcher inside the included files.
          CLIApp $true when a standalone executable is a command-line tool; it is then
                                  installed to the APF bin folder that is added to PATH.
          Files The files to deploy (PowerShellProfiles and Files).
          FilesDirectoryName The name of the directory the files are deployed to.

        Script-App, Script-User and Custom packages use the "script" template: the main installer runs
        the pre-install script, the deployment script and the post-install script (each with -Uninstall
        for the uninstall command) and saves the configuration that the detection script checks.
          - Script-App runs your script in the system context.
          - Script-User runs your script in the user context (assign the Intune app to run as user).
          - Custom adds a placeholder deployment script, Intune-Custom.ps1, for you to complete.

    .PARAMETER ConfigurationType
        The type of configuration package to create: Registry, PowerShellProfiles, Files, Script-OS,
        Script-App, Script-User, StandAlone-Exe, Standalone-Application, WindowsFeature or Custom.
        Script-App, Script-User and Custom build a script package; see the description.

    .OUTPUTS
        System.String
        Messages that describe where the package was created and which install commands to use.

    .EXAMPLE
        New-APFConfigDeployment -ConfigurationType Registry -Name "My Settings" -Version "1.0.0.0" -RegistryValue "HKLM:\Software,MySoftware,MyValue,String,MyData,ADD"

        Creates a registry deployment package with one registry entry.

    .EXAMPLE
        New-APFConfigDeployment -ConfigurationType Files -Name "My Files" -Version "1.0.0.0" -Files "C:\file1.txt", "C:\file2.txt" -FilesDirectoryName "MyFiles"

        Creates a package that deploys two files to the MyFiles directory.

    .EXAMPLE
        New-APFConfigDeployment -ConfigurationType StandAlone-Exe -Name "MyApp" -Version "1.2.3.4" -Path "C:\path\to\myapp.exe" -CLIApp $true

        Creates a package that installs myapp.exe as a command-line tool.

    .NOTES
        The generated installer scripts run on Windows devices managed by Intune.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true, HelpMessage = "The type of configuration this command is for.")]
        [ValidateSet("Registry", "PowerShellProfiles", "Files", "Script-OS", "Script-App", "Script-User", "StandAlone-Exe", "Standalone-Application", "WindowsFeature", "Custom")]
        [string]$ConfigurationType
    )

    DynamicParam {
        $paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
        # Default Params - doing this so the only default choice is the Configuration Type
        $PathValidation = {
            foreach ($i in $_) {
                if (-not (Test-Path $i)) {
                    throw "Path $($i) is invalid, double check and try again."
                }
            }
            return $true
        }
        $defaults = @(
            @{Name = "Name"; ParameterType = [string]; Mandatory = $true; Position = 1; ValueFromPipelineByPropertyName = $true; HelpMessage = "The name of the deployment. This is written in to the exported configuration files." }
            @{Name = "Version"; ParameterType = [version]; Mandatory = $true; Position = 2; ValueFromPipelineByPropertyName = $true; HelpMessage = "The version of the deployment in the format x.x.x.x. This is written in to the exported configuration files." }
            @{Name = "Path"; ParameterType = [string]; Mandatory = $true; Position = 3; ValueFromPipelineByPropertyName = $true; HelpMessage = "The Path or either the main file or directory of a standalone application"; ValidateScript = $PathValidation }
            @{Name = "DestinationFolder"; ParameterType = [string]; ValueFromPipelineByPropertyName = $true; HelpMessage = "The folder where the output will be created. Default is the current directory." }
            @{Name = "CreateIntuneWinPackage"; ParameterType = [switch]; ValueFromPipelineByPropertyName = $true; HelpMessage = "Create a Intune package for the application. Default is false." }
        )
        foreach ($d in $defaults) {
            $param = $(New-DynamicParameter @d)
            $paramDictionary.Add($param.Name, $param.Parameter)
        }
        $variableParams = @(
            @{Name = "IncludedFiles"; ParameterType = [string[]]; ValueFromPipelineByPropertyName = $true; HelpMessage = "Additional files or folders to include in the package."; ValidateScript = $PathValidation }
            @{Name = "RegistryValue"; ParameterType = [string]; ValueFromPipelineByPropertyName = $true; HelpMessage = "One registry entry as 'RegistryPath,KeyName,ValueName,ValueType,ValueData,State', for example 'HKLM:\Software,MySoftware,MyValue,String,MyData,ADD'. State is ADD, MODIFY or REMOVE." }
            @{Name = "Target"; ParameterType = [string]; ValidateSet = "System", "User"; HelpMessage = "The target for the deployment, User context or System context. Default is 'System'." }
            @{Name = "LauncherName"; ParameterType = [string]; HelpMessage = "Name of the file to launch the application." }
            @{Name = "LauncherRelativePath"; ParameterType = [string]; HelpMessage = "The relative path of the launcher file in the included files directory path." }
            @{Name = "CLIApp"; ParameterType = [bool]; HelpMessage = "If the standalone-exe is a cli application, add it to the bin directory and ensure it's added to the users PATH." }
            @{Name = "Files"; ParameterType = [string[]]; HelpMessage = "List of files to include in the package."; ValidateScript = $PathValidation }
            @{Name = "FilesDirectoryName"; ParameterType = [string]; Mandatory = $true; HelpMessage = "Name of the directory where the files will be stored." }
        )
        $selected = switch ($ConfigurationType) {
            'Registry' { 0, 1, 2 }
            { $_ -in 'Files', 'PowerShellProfiles' } { 6, 7 }
            { $_ -in 'Script-OS', 'WindowsFeature' } { 0 }
            { $_ -in 'Script-App', 'Script-User' } { 0 }
            'Custom' { 0, 2 }
            'StandAlone-Exe' { 0, 5 }
            'Standalone-Application' { 0, 3, 4 }
            default { $null }
        }
        if ($null -eq $selected) {
            return
        }
        foreach ($index in $selected) {
            $definition = $variableParams[$index]
            $param = $(New-DynamicParameter @definition)
            if (-not $paramDictionary.ContainsKey($param.Name)) {
                $paramDictionary.Add($param.Name, $param.Parameter)
            }
        }
        if ($ConfigurationType -notin 'StandAlone-Exe', 'Standalone-Application', 'Script-App', 'Script-User') {
            $null = $paramDictionary.Remove('Path')
        }
        return $paramDictionary
    }

    begin {
        ## Convert bound dynamic params to variables
        $DestinationFolder = $PSBoundParameters['DestinationFolder']
        if ([string]::IsNullOrEmpty($DestinationFolder)) {
            $DestinationFolder = $PWD.Path
        }
        $Name = $PSBoundParameters['Name']
        $Version = $PSBoundParameters['Version']
        $Path = $PSBoundParameters['Path']
        $IncludedFiles = @($PSBoundParameters['IncludedFiles'] | Where-Object { $_ })
        $CreateIntuneWinPackage = [bool]$PSBoundParameters['CreateIntuneWinPackage']
        $RegistryValue = $PSBoundParameters['RegistryValue']
        $Target = $PSBoundParameters['Target']
        if ([string]::IsNullOrEmpty($Target)) {
            $Target = 'System'
        }
        $LauncherName = $PSBoundParameters['LauncherName']
        $LauncherRelativePath = $PSBoundParameters['LauncherRelativePath']
        $CLIApp = [bool]$PSBoundParameters['CLIApp']
        $FilesDirectoryName = $PSBoundParameters['FilesDirectoryName']
        $Files = @($PSBoundParameters['Files'] | Where-Object { $_ })
        $TemplateRoot = Join-Path -Path $PSScriptRoot -ChildPath 'Templates'
        $PackagedBy = [Environment]::UserName

        # Creates the package folder, or asks before deleting and recreating an existing one
        function Initialize-PackageFolder {
            param ([string]$FolderPath)
            if (Test-Path -Path $FolderPath) {
                if ($PSCmdlet.ShouldContinue("Overwrite existing folder '$FolderPath' for the deployment? Warning: This will recursively delete all files in the folder.", "Confirm Overwrite")) {
                    Write-Verbose "Removing existing directory $FolderPath and recreating it."
                    Remove-Item -Path $FolderPath -Recurse -Force -ErrorAction Stop
                    $null = New-Item -Path $FolderPath -ItemType Directory -ErrorAction Stop
                }
            }
            else {
                Write-Verbose "Creating package folder $FolderPath"
                $null = New-Item -Path $FolderPath -ItemType Directory -ErrorAction Stop
            }
        }

        # Sets a value on the imported template configuration, adding the property when it is missing
        function Add-TemplateValue {
            param ($Config, [string]$Property, $Value)
            $Config | Add-Member -NotePropertyName $Property -NotePropertyValue $Value -Force
        }

        function Copy-IncludedItem {
            param ([string[]]$Item, [string]$Destination)
            $count = 0
            foreach ($i in $Item) {
                $count++
                Write-Verbose "Copying $i. [$count of $($Item.Count)]"
                Copy-Item -Path $i -Destination $Destination -Recurse -Force -ErrorAction Stop
            }
        }

        function Edit-DetectionScript {
            param ([string]$ScriptPath, [hashtable]$Replacements)
            $Content = Get-Content -Path $ScriptPath -Raw -ErrorAction Stop
            foreach ($Key in $Replacements.Keys) {
                $Content = $Content.Replace($Key, [string]$Replacements[$Key])
            }
            Set-Content -Path $ScriptPath -Value $Content -NoNewline -ErrorAction Stop
        }
    }

    process {
        $TelemetryArgs = @{
            ModuleName    = $MyInvocation.MyCommand.Module.Name
            ModuleVersion = [string]$MyInvocation.MyCommand.Module.Version
            CommandName   = $MyInvocation.MyCommand.Name
            ExecutionID   = [guid]::NewGuid().ToString()
        }
        Invoke-TelemetryCollection @TelemetryArgs -Stage Start -ClearTimer

        try {
            if ($ConfigurationType -in @('Script-App', 'Script-User') -and ([System.IO.Path]::GetExtension($Path) -ne '.ps1')) {
                throw "For $ConfigurationType, Path must be the PowerShell deployment script (.ps1); '$Path' is not."
            }

            $PackageFolder = Join-Path -Path $DestinationFolder -ChildPath $Name
            if (-not $PSCmdlet.ShouldProcess($PackageFolder, "Create $ConfigurationType deployment package")) {
                Invoke-TelemetryCollection @TelemetryArgs -Stage End
                return
            }
            Initialize-PackageFolder -FolderPath $PackageFolder
            $ConfigPath = Join-Path -Path $PackageFolder -ChildPath 'config.installer.json'
            $SetupFile = Join-Path -Path $PackageFolder -ChildPath 'Intune-I-MainInstaller.ps1'

            switch ($ConfigurationType) {
                "Registry" {
                    $TemplateFolder = Join-Path -Path $TemplateRoot -ChildPath 'Registry'
                    $RegistryFile = Join-Path -Path $PackageFolder -ChildPath "$($Name)_Registry.csv"
                    Write-Verbose "Creating registry file at $RegistryFile"
                    Copy-Item -Path (Join-Path -Path $TemplateFolder -ChildPath 'registry_entries.config.csv') -Destination $RegistryFile -Force -ErrorAction Stop

                    if ($RegistryValue) {
                        # Add the supplied entry to the registry CSV unless it is already there
                        $Header = @((Get-Content -Path $RegistryFile -TotalCount 1).Split(','))
                        $Fields = @($RegistryValue.Split(','))
                        if ($Fields.Count -ne $Header.Count) {
                            throw "RegistryValue must contain $($Header.Count) comma-separated values ($($Header -join ',')); '$RegistryValue' contains $($Fields.Count)."
                        }
                        $Entry = [ordered]@{}
                        for ($i = 0; $i -lt $Header.Count; $i++) {
                            $Entry[$Header[$i]] = $Fields[$i].Trim()
                        }
                        $Entry = [PSCustomObject]$Entry
                        $Existing = @(Import-Csv -Path $RegistryFile -ErrorAction Stop)
                        $Duplicate = $Existing | Where-Object { ($_.PSObject.Properties.Value -join ',') -eq ($Entry.PSObject.Properties.Value -join ',') }
                        if ($Duplicate) {
                            Write-Warning "The registry value '$RegistryValue' already exists in the file."
                        }
                        else {
                            @($Existing) + $Entry | Export-Csv -Path $RegistryFile -NoTypeInformation -Force -ErrorAction Stop
                        }
                    }
                    else {
                        Write-Verbose "No registry keys were supplied."
                    }

                    $IncludeFolder = Join-Path -Path $PackageFolder -ChildPath "src"
                    $null = New-Item -Path $IncludeFolder -ItemType Directory -Force -ErrorAction Stop
                    Copy-IncludedItem -Item $IncludedFiles -Destination $PackageFolder

                    Copy-Item -Path (Join-Path -Path $TemplateFolder -ChildPath '*') -Destination $PackageFolder -Recurse -Exclude '*.md', '*config.*' -ErrorAction Stop

                    $MainConfig = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath 'config.installer.json') -Raw -ErrorAction Stop | ConvertFrom-Json
                    Add-TemplateValue -Config $MainConfig -Property 'name' -Value $Name
                    Add-TemplateValue -Config $MainConfig -Property 'version' -Value $Version.ToString()
                    Add-TemplateValue -Config $MainConfig -Property 'target' -Value $Target
                    Add-TemplateValue -Config $MainConfig -Property 'registryfile' -Value "$($Name)_Registry.csv"
                    Add-TemplateValue -Config $MainConfig -Property 'packagedby' -Value $PackagedBy
                    $MainConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $ConfigPath -ErrorAction Stop

                    Edit-DetectionScript -ScriptPath (Join-Path -Path $PackageFolder -ChildPath 'Intune-D-RegistryDetection.ps1') -Replacements @{
                        '##NAME_TEMPLATE'    = $Name
                        '##VERSION_TEMPLATE' = $Version.ToString()
                    }
                }
                { $_ -in "PowerShellProfiles", "Files" } {
                    $TemplateFolder = Join-Path -Path $TemplateRoot -ChildPath $(if ($ConfigurationType -eq 'Files') { 'Files' } else { 'PowerShellProfile' })
                    Write-Verbose "Copying template files to destination folder."
                    Copy-Item -Path (Join-Path -Path $TemplateFolder -ChildPath '*') -Destination $PackageFolder -Recurse -ErrorAction Stop
                    Copy-IncludedItem -Item $Files -Destination $PackageFolder

                    $FileNames = @($Files | ForEach-Object { Split-Path -Path $_ -Leaf }) -join ','
                    $MainConfig = Get-Content -Path $ConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json
                    Add-TemplateValue -Config $MainConfig -Property 'name' -Value $Name
                    Add-TemplateValue -Config $MainConfig -Property 'version' -Value $Version.ToString()
                    Add-TemplateValue -Config $MainConfig -Property 'target' -Value 'system'
                    Add-TemplateValue -Config $MainConfig -Property 'directory' -Value $FilesDirectoryName
                    Add-TemplateValue -Config $MainConfig -Property 'files' -Value $FileNames
                    Add-TemplateValue -Config $MainConfig -Property 'packagedby' -Value $PackagedBy
                    Write-Verbose "Modifying template files with template parameters."
                    $MainConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $ConfigPath -ErrorAction Stop

                    Edit-DetectionScript -ScriptPath (Join-Path -Path $PackageFolder -ChildPath 'Intune-D-AppDetection.ps1') -Replacements @{
                        '##NAME_TEMPLATE'    = $Name
                        '##VERSION_TEMPLATE' = $Version.ToString()
                        '##FILES_TEMPLATE'   = $FileNames
                    }
                }
                "StandAlone-Exe" {
                    Copy-Item -Path $Path -Destination $PackageFolder -ErrorAction Stop
                    Copy-IncludedItem -Item $IncludedFiles -Destination $PackageFolder
                    Write-Verbose "Copying template files to destination folder."
                    Copy-Item -Path (Join-Path -Path (Join-Path -Path $TemplateRoot -ChildPath 'standalone-exe') -ChildPath '*') -Destination $PackageFolder -Recurse -ErrorAction Stop

                    $FileName = Split-Path -Path $Path -Leaf
                    $MainConfig = Get-Content -Path $ConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json
                    Add-TemplateValue -Config $MainConfig -Property 'name' -Value $Name
                    Add-TemplateValue -Config $MainConfig -Property 'version' -Value $Version.ToString()
                    # The installer script reads the executable name from 'filename'
                    Add-TemplateValue -Config $MainConfig -Property 'filename' -Value $FileName
                    Add-TemplateValue -Config $MainConfig -Property 'includedfiles' -Value (@($IncludedFiles | ForEach-Object { Split-Path -Path $_ -Leaf }) -join ',')
                    Add-TemplateValue -Config $MainConfig -Property 'cli' -Value $CLIApp
                    Add-TemplateValue -Config $MainConfig -Property 'packagedby' -Value $PackagedBy
                    Write-Verbose "Modifying template files with template parameters."
                    $MainConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $ConfigPath -ErrorAction Stop

                    Edit-DetectionScript -ScriptPath (Join-Path -Path $PackageFolder -ChildPath 'Intune-D-AppDetection.ps1') -Replacements @{
                        '##NAME_TEMPLATE'     = $Name
                        '##VERSION_TEMPLATE'  = $Version.ToString()
                        '##FILENAME_TEMPLATE' = $FileName
                    }
                }
                { $_ -in "Script-OS", "WindowsFeature" } {
                    if ($ConfigurationType -eq 'Script-OS') {
                        $TemplateFolder = Join-Path -Path $TemplateRoot -ChildPath 'script-os'
                        $EntriesTemplate = 'os-config_entries.config.csv'
                        $DetectionFile = 'Intune-D-Detection.ps1'
                    }
                    else {
                        $TemplateFolder = Join-Path -Path $TemplateRoot -ChildPath 'WindowsFeatures'
                        $EntriesTemplate = 'windowsfeatures_entries.config.csv'
                        $DetectionFile = 'Intune-D-WindowsFeatureDetection.ps1'
                    }
                    # The installer scripts read "<Name>_config.csv"
                    $ConfigFileName = "$($Name)_config.csv"
                    $ConfigFile = Join-Path -Path $PackageFolder -ChildPath $ConfigFileName
                    Write-Verbose "Creating configuration file at $ConfigFile"
                    Copy-Item -Path (Join-Path -Path $TemplateFolder -ChildPath $EntriesTemplate) -Destination $ConfigFile -Force -ErrorAction Stop

                    $IncludeFolder = Join-Path -Path $PackageFolder -ChildPath "src"
                    $null = New-Item -Path $IncludeFolder -ItemType Directory -Force -ErrorAction Stop
                    Copy-IncludedItem -Item $IncludedFiles -Destination $PackageFolder

                    Copy-Item -Path (Join-Path -Path $TemplateFolder -ChildPath '*') -Destination $PackageFolder -Recurse -Exclude '*.md', '*config.*' -ErrorAction Stop

                    $MainConfig = Get-Content -Path (Join-Path -Path $TemplateFolder -ChildPath 'config.installer.json') -Raw -ErrorAction Stop | ConvertFrom-Json
                    Add-TemplateValue -Config $MainConfig -Property 'name' -Value $Name
                    Add-TemplateValue -Config $MainConfig -Property 'version' -Value $Version.ToString()
                    Add-TemplateValue -Config $MainConfig -Property 'configfile' -Value $ConfigFileName
                    Add-TemplateValue -Config $MainConfig -Property 'includedfiles' -Value (@($IncludedFiles | ForEach-Object { Split-Path -Path $_ -Leaf }) -join ',')
                    Add-TemplateValue -Config $MainConfig -Property 'packagedby' -Value $PackagedBy
                    $MainConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $ConfigPath -ErrorAction Stop

                    Edit-DetectionScript -ScriptPath (Join-Path -Path $PackageFolder -ChildPath $DetectionFile) -Replacements @{
                        '##NAME_TEMPLATE'    = $Name
                        '##VERSION_TEMPLATE' = $Version.ToString()
                    }
                }
                { $_ -in "Script-App", "Script-User", "Custom" } {
                    $TemplateFolder = Join-Path -Path $TemplateRoot -ChildPath 'script'
                    Write-Verbose "Copying template files to destination folder."
                    Copy-Item -Path (Join-Path -Path $TemplateFolder -ChildPath '*') -Destination $PackageFolder -Recurse -Exclude '*.md', 'Intune-Custom.ps1' -ErrorAction Stop
                    if ($ConfigurationType -eq 'Custom') {
                        Copy-Item -Path (Join-Path -Path $TemplateFolder -ChildPath 'Intune-Custom.ps1') -Destination $PackageFolder -ErrorAction Stop
                        $ScriptFile = 'Intune-Custom.ps1'
                        $ScriptTarget = $Target.ToLowerInvariant()
                    }
                    else {
                        Copy-Item -Path $Path -Destination $PackageFolder -ErrorAction Stop
                        $ScriptFile = Split-Path -Path $Path -Leaf
                        $ScriptTarget = $(if ($ConfigurationType -eq 'Script-User') { 'user' } else { 'system' })
                    }
                    Copy-IncludedItem -Item $IncludedFiles -Destination $PackageFolder

                    $MainConfig = Get-Content -Path $ConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json
                    Add-TemplateValue -Config $MainConfig -Property 'name' -Value $Name
                    Add-TemplateValue -Config $MainConfig -Property 'version' -Value $Version.ToString()
                    Add-TemplateValue -Config $MainConfig -Property 'target' -Value $ScriptTarget
                    Add-TemplateValue -Config $MainConfig -Property 'scriptfile' -Value $ScriptFile
                    Add-TemplateValue -Config $MainConfig -Property 'includedfiles' -Value (@($IncludedFiles | ForEach-Object { Split-Path -Path $_ -Leaf }) -join ',')
                    Add-TemplateValue -Config $MainConfig -Property 'packagedby' -Value $PackagedBy
                    $MainConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $ConfigPath -ErrorAction Stop

                    Edit-DetectionScript -ScriptPath (Join-Path -Path $PackageFolder -ChildPath 'Intune-D-Detection.ps1') -Replacements @{
                        '##NAME_TEMPLATE'    = $Name
                        '##VERSION_TEMPLATE' = $Version.ToString()
                    }
                }
                "Standalone-Application" {
                    Copy-Item -Path $Path -Destination $PackageFolder -Recurse -ErrorAction Stop
                    Copy-IncludedItem -Item $IncludedFiles -Destination $PackageFolder
                    Write-Verbose "Copying template files to destination folder."
                    Copy-Item -Path (Join-Path -Path (Join-Path -Path $TemplateRoot -ChildPath 'standalone-application') -ChildPath '*') -Destination $PackageFolder -Recurse -Exclude '*.md' -ErrorAction Stop

                    $MainConfig = Get-Content -Path $ConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json
                    Add-TemplateValue -Config $MainConfig -Property 'name' -Value $Name
                    Add-TemplateValue -Config $MainConfig -Property 'version' -Value $Version.ToString()
                    # The installer script reads the deployed item from 'filename'
                    Add-TemplateValue -Config $MainConfig -Property 'filename' -Value (Split-Path -Path $Path -Leaf)
                    Add-TemplateValue -Config $MainConfig -Property 'launchername' -Value ([string]$LauncherName)
                    Add-TemplateValue -Config $MainConfig -Property 'launcherrelativepath' -Value ([string]$LauncherRelativePath)
                    Add-TemplateValue -Config $MainConfig -Property 'includedfiles' -Value (@($IncludedFiles | ForEach-Object { Split-Path -Path $_ -Leaf }) -join ',')
                    Write-Verbose "Modifying template files with template parameters."
                    $MainConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $ConfigPath -ErrorAction Stop

                    Edit-DetectionScript -ScriptPath (Join-Path -Path $PackageFolder -ChildPath 'Intune-D-AppDetection.ps1') -Replacements @{
                        '##NAME_TEMPLATE'                 = $Name
                        '##VERSION_TEMPLATE'              = $Version.ToString()
                        '##LAUNCHERNAME_TEMPLATE'         = $LauncherName
                        '##LAUNCHERRELATIVEPATH_TEMPLATE' = $LauncherRelativePath
                    }
                }
            }

            Write-Output "The deployment '$Name' has been successfully packaged.`nThis can be found in the folder '$PackageFolder'."
            if ($CreateIntuneWinPackage) {
                $Package = New-APFIntuneWinPackage -SourceFolder $PackageFolder -SetupFile $SetupFile -OutputFolder $DestinationFolder -PackageName $Name -ErrorAction Stop
                if ($Package) {
                    Write-Output "The deployment '$Name' was also packaged to an intunewin file.`nThis can be found at '$($Package.FullName)'."
                }
            }
            Write-Output "When publishing the deployment to Intune, use`n'powershell.exe -ExecutionPolicy RemoteSigned -File Intune-I-MainInstaller.ps1' for the install Command and`n'powershell.exe -ExecutionPolicy RemoteSigned -File Intune-I-MainInstaller.ps1 -Uninstall' for the Uninstall Command."
            Invoke-TelemetryCollection @TelemetryArgs -Stage End
        }
        catch {
            Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $_
            Write-Error -Message "Failed to create the $ConfigurationType deployment package: $($_.Exception.Message)" -Exception $_.Exception
        }
    }
}