Public/New-Win32App.ps1

<#
.Synopsis
Created on: 14/03/2021
Updated on: 08/01/2025
Created by: Ben Whitmore
Filename: New-Win32App.ps1
 
The Win32 App Migration Tool is designed to inventory ConfigMgr Applications and Deployment Types, build .intunewin files and create Win3 apps in The Intune admin center.
The script supports different authentication methods for connecting to the Microsoft Graph API. The preferred method is using a certificate, as this method is more secure. If you don't have a certificate, the Delegate and devicecode flow are also supported. Client Secret is an option too, but it is not recommended for production environments.
Documentation can be found at https://github.com/byteben/Win32App-Migration-Tool/blob/main/README.md
 
.Description
This script is designed to export the Application and Deployment Data from ConfigMgr to firstly create an .intunewin file and secondly publish the Win32 app to Intune
Change Control Log: https://github.com/byteben/Win32App-Migration-Tool/blob/main/Release_Notes.md
 
.NOTES
PowerShell 5.1 or later is required to run this script.
PowerShell 7 or later is recommended for best performance.
Some Az storage cmdlets for upload verification will be skipped if not running in PowerShell 7 or later.
 
---------------------------------------------------------------------------------
LEGAL DISCLAIMER
 
This solution is distributed under the GNU GENERAL PUBLIC LICENSE
 
The PowerShell script provided is shared with the community as-is
The author and co-author(s) make no warranties or guarantees regarding its functionality, reliability, or suitability for any specific purpose
Please note that the script may need to be modified or adapted to fit your specific environment or requirements
It is recommended to thoroughly test the script in a non-production environment before using it in a live or critical system
The author and co-author(s) cannot be held responsible for any damages, losses, or adverse effects that may arise from the use of this script
You assume all risks and responsibilities associated with its usage
---------------------------------------------------------------------------------
 
.PARAMETER LogId
The component (script name) passed as LogID to the 'Write-Log' function.
 
.PARAMETER AppName
Pass a string to the toll to search for applications in ConfigMgr
 
.PARAMETER DownloadContent
When passed, the content for the deployment type is saved locally to the working folder "Content"
 
.PARAMETER SiteCode
Specify the Sitecode you wish to connect to. We try to get this auitomatically from the SMS_ProviderLocation WMI class if non is specified
 
.PARAMETER ProviderMachineName
Specify the SMS Provider you wish to connect to
 
.PARAMETER ExportIcon
When passed, the Application icon is decoded from base64 and saved to the Logos folder
 
.PARAMETER WorkingFolder
This is the working folder for the Win32AppMigration Tool.
Note: Care should be given when specifying the working folder because downloaded content can increase the working folder size considerably
 
.PARAMETER PackageApps
Pass this parameter to package selected apps in the .intunewin format
 
.PARAMETER CreateApps
Pass this parameter to create the Win32apps in Intune
 
.PARAMETER ResetLog
Pass this parameter to reset the log file
 
.PARAMETER ExcludePMPC
Pass this parameter to exclude apps created by PMPC from the results. Filter is applied to Application "Comments". string can be modified in Get-AppList Function
 
.PARAMETER ExcludeFilter
Pass this parameter to exclude specific apps from the results. string value that accepts wildcards e.g. "Microsoft*"
 
.PARAMETER NoOgv
When passed, the Out-Gridview is suppressed
 
.PARAMETER Win32ContentPrepToolUri
URI for Win32 Content Prep Tool
 
.PARAMETER OverrideIntuneWin32FileName
Override intunewin filename. Default is the name calcualted from the install command line. You only need to pass the file name, not the extension
 
.PARAMETER Win32AppNotes
Notes field value to add to the Win32App JSON body
 
.PARAMETER AllowAvailableUninstall
When creating the Win32App, allow the user to uninstall the app if it is available in the Company Portal
 
.PARAMETER TenantId
Tenant Id or name to connect to. This parameter is mandatory for obtaining a token
 
.PARAMETER ClientId
Client Id (App Registration) to connect to. This parameter is mandatory for obtaining a token
 
.PARAMETER ClientSecret
Client Secret for the App Registration. This parameter is mandatory for obtaining a token
 
.PARAMETER CertificateThumbprint
Client certificate thumbprint for authentication. This parameter is mandatory for obtaining a token
 
.PARAMETER UseDeviceAuthentication
Use device authentication instead of user authentication. This parameter is mandatory if you want to use device authentication.
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *"
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -DownloadContent
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -ExportLogo
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -ExportLogo -PackageApps
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -ExportLogo -PackageApps -CreateApps -TenantId "1234-1234-1234" -ClientId "1234-1234-1234" -ClientSecret "1234-1234-1234"
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -ExportLogo -PackageApps -CreateApps -ResetLog -TenantId "1234-1234-1234" -ClientId "1234-1234-1234" -ClientSecret "1234-1234-1234"
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -ExportLogo -PackageApps -CreateApps -ResetLog -ExcludePMPC -TenantId "1234-1234-1234" -ClientId "1234-1234-1234" -ClientSecret "1234-1234-1234"
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -ExportLogo -PackageApps -CreateApps -ResetLog -ExcludePMPC -ExcludeFilter "Microsoft*" -TenantId "1234-1234-1234" -ClientId "1234-1234-1234" -ClientSecret "1234-1234-1234"
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -ExportLogo -PackageApps -CreateApps -ResetLog -ExcludePMPC -ExcludeFilter "Microsoft*" -OverrideIntuneWin32FileName "application" -TenantId "1234-1234-1234" -ClientId "1234-1234-1234" -ClientSecret "1234-1234-1234"
 
.EXAMPLE
New-Win32App -ProviderMachineName "SCCM1.byteben.com" -AppName "Microsoft Edge Chromium *" -ExportLogo -PackageApps -CreateApps -ResetLog -ExcludePMPC -ExcludeFilter "Microsoft*" -OverrideIntuneWin32FileName "application" -Win32AppNotes "Created by the Win32App Migration Tool" -TenantId "1234-1234-1234" -ClientId "1234-1234-1234" -ClientSecret "1234-1234-1234"
 
#>

function New-Win32App {

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 0, HelpMessage = 'The Site Code of the ConfigMgr Site')]
        [ValidatePattern('(?##The Site Code must be only 3 alphanumeric characters##)^[a-zA-Z0-9]{3}$')]
        [string]$SiteCode,
        [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 1, HelpMessage = 'Server name that has an SMS Provider site system role')]
        [string]$ProviderMachineName,  
        [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 2, HelpMessage = 'The name of the application to search for. Accepts wildcards *')]
        [string]$AppName,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 3, HelpMessage = 'DownloadContent: When passed, the content for the deployment type is saved locally to the working folder "Content"')]
        [Switch]$DownloadContent,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 4, HelpMessage = 'ExportLogo: When passed, the Application icon is decoded from base64 and saved to the Logos folder')]
        [Switch]$ExportIcon,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 5, HelpMessage = 'The working folder for the Win32AppMigration Tool. Care should be given when specifying the working folder because downloaded content can increase the working folder size considerably')]
        [string]$workingFolder = "C:\Win32AppMigrationTool",
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 6, HelpMessage = 'PackageApps: Pass this parameter to package selected apps in the .intunewin format')]
        [Switch]$PackageApps,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 7, HelpMessage = 'ResetLog: Pass this parameter to reset the log file')]
        [Switch]$ResetLog,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 8, HelpMessage = 'ExcludePMPC: Pass this parameter to exclude apps created by PMPC from the results. Filter is applied to Application "Comments". string can be modified in Get-AppList Function')]
        [Switch]$ExcludePMPC,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 9, HelpMessage = 'ExcludeFilter: Pass this parameter to exclude specific apps from the results. string value that accepts wildcards e.g. "Microsoft*"')]
        [string]$ExcludeFilter,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 10, HelpMessage = 'NoOGV: When passed, the Out-Gridview is suppressed')]
        [Switch]$NoOgv,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 11, HelpMessage = 'URI for Win32 Content Prep Tool')]
        [string]$Win32ContentPrepToolUri = 'https://raw.githubusercontent.com/microsoft/Microsoft-Win32-Content-Prep-Tool/refs/heads/master/IntuneWinAppUtil.exe',
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 12, HelpMessage = 'Override intunewin filename. Default is the name calculated from the install command line')]
        [string]$OverrideIntuneWin32FileName,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 13, HelpMessage = 'Notes field value to add to the Win32App JSON body')]
        [string]$Win32AppNotes = "Created by the Win32App Migration Tool",
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 14, HelpMessage = "When creating the Win32App, allow the user to uninstall the app if it is available in the Company Portal")]
        [bool]$AllowAvailableUninstall = $true,
        [Parameter(Mandatory = $false, Position = 15, HelpMessage = 'CreateApps: Pass this parameter to create the Win32apps in Intune')]
        [Switch]$CreateApps,

        # Shared Parameters for Graph Authentication
        [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret', Position = 16, HelpMessage = 'Tenant Id or name to connect to')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ClientCertificateThumbprint', Position = 16, HelpMessage = 'Tenant Id or name to connect to')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDeviceAuthentication', Position = 16, HelpMessage = 'Tenant Id or name to connect to')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Interactive', Position = 16, HelpMessage = 'Tenant Id or name to connect to')]
        [string]$TenantId,

        [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret', Position = 17, HelpMessage = 'Client Id (App Registration) to connect to')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ClientCertificateThumbprint', Position = 17, HelpMessage = 'Client Id (App Registration) to connect to')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDeviceAuthentication', Position = 17, HelpMessage = 'Client Id (App Registration) to connect to')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Interactive', Position = 17, HelpMessage = 'Client Id (App Registration) to connect to')]
        [string]$ClientId,

        # AuthN and AuthZ Parameters
        [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret', Position = 18, HelpMessage = 'Client secret for authentication')]
        [string]$ClientSecret,
        [Parameter(Mandatory = $true, ParameterSetName = 'ClientCertificateThumbprint', Position = 18, HelpMessage = 'Client certificate thumbprint for authentication')]
        [string]$ClientCertificateThumbprint,
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDeviceAuthentication', Position = 18, HelpMessage = 'Use device authentication for Microsoft Graph API')]
        [switch]$UseDeviceAuthentication,
        [Parameter(Mandatory = $false, Position = 19, HelpMessage = 'The scopes required for Microsoft Graph API access')]
        [string[]]$RequiredScopes = ('DeviceManagementApps.ReadWrite.All'),
    
        # Additional Parameters
        [Parameter(Mandatory = $false, ValuefromPipeline = $false, HelpMessage = "The component (script name) passed as LogID to the 'Write-Log' function")]
        [string]$LogId = $($MyInvocation.MyCommand).Name
        
    )
    begin {

        # Add logic to check for CreateApps parameter and validate TenantId and ClientId
        if ($PSCmdlet.ParameterSetName -eq 'CreateAppsSet') {
            if (-not $TenantId) {
                Write-Error -Message "TenantId is required when CreateApps is specified." -Category InvalidArgument -ErrorAction Stop
            }
            if (-not $ClientId) {
                Write-Error -Message "ClientId is required when CreateApps is specified." -Category InvalidArgument -ErrorAction Stop
            }
        }
    }

    process {

        # Create global variable(s)
        $global:workingFolder_Root = $workingFolder
        $global:scopes = $RequiredScopes

        #region Prepare_Workspace
        # Initialize folders to prepare workspace for logging
        Write-Host "Initializing required folders..." -ForegroundColor Cyan

        foreach ($folder in $workingFolder_Root, "$workingFolder_Root\Logs") {
            if (-not (Test-Path -Path $folder)) {
                Write-Host ("Working folder root does not exist at '{0}'. Creating environemnt..." -f $folder) -ForegroundColor Cyan
                New-Item -Path $folder -ItemType Directory -Force -ErrorAction Stop | Out-Null
            }
            else {
                Write-Host ("Folder '{0}' already exists. Skipping folder creation" -f $folder) -ForegroundColor Yellow
            }
        }

        # Rest the log file if the -ResetLog parameter is passed
        if ($ResetLog -and (Test-Path -Path "$workingFolder_Root\Logs") ) {
            Write-Log -Message $null -ResetLogFile
        }
        #endregion

        # Begin Script
        New-VerboseRegion -Message 'Start Win32AppMigrationTool' -ForegroundColor 'Gray'

        $ScriptRoot = $PSScriptRoot
        Write-Log -Message ("ScriptRoot is '{0}'" -f $ScriptRoot) -LogId $LogId

        # Connect to Site Server
        if ($PSBoundParameters.ContainsKey('SiteCode')) {
            Connect-SiteServer -SiteCode $SiteCode -ProviderMachineName $ProviderMachineName
        } 
        else {
            Connect-SiteServer -ProviderMachineName $ProviderMachineName
        }

        # Check the folder structure for the working directory and create if necessary
        New-VerboseRegion -Message 'Checking Win32AppMigrationTool folder structure' -ForegroundColor 'Gray'

        #region Create_Folders
        Write-Host "Creating additionl folders..." -ForegroundColor Cyan
        Write-Log -Message ("New-FolderToCreate -Root '{0}' -FolderNames @('Icons', 'Content', 'ContentPrepTool', 'DetectionMethods','Details', 'Win32Apps')" -f $workingFolder_Root) -LogId $LogId
        New-FolderToCreate -Root $workingFolder_Root -FolderNames @('Icons', 'Content', 'ContentPrepTool', 'DetectionMethods', 'Details', 'Win32Apps')
        #endRegion

        #region Get_Content_Tool
        New-VerboseRegion -Message 'Checking if the Win32contentpreptool is required' -ForegroundColor 'Gray'

        # Download the Win32 Content Prep Tool if the PackageApps parameter is passed and the existing content prep tool is older than 30 days
        if ($PackageApps) {
            $fileDestination = Join-Path -Path "$workingFolder_Root\ContentPrepTool" -ChildPath "IntuneWinAppUtil.exe"

            if (Test-Path $fileDestination) {
                $fileAge = (Get-Item $fileDestination).LastWriteTime
                $fileAgeInDays = (New-TimeSpan -Start $fileAge -End (Get-Date)).Days

                if ($fileAgeInDays -gt 30) {
                    Write-LogAndHost -Message ("Information: IntuneWinAppUtil.exe is {0} days old. Downloading new version" -f $fileAgeInDays) -LogId $LogId -Severity 2 -ForegroundColor Yellow
                    Get-FileFromInternet -Uri $Win32ContentPrepToolUri -Destination $fileDestination
                }
                else {
                    Write-LogAndHost ("Information: IntuneWinAppUtil.exe is {0} days old. Skipping download" -f $fileAgeInDays) -LogId $LogId -Severity 2 -ForegroundColor Yellow
                }
            }
            else {
                Write-LogAndHost -Message ("Get-FileFromInternet -URI '{0} -Destination {1}" -f $Win32ContentPrepToolUri, $fileDestination) -LogId $LogId -ForegroundColor Cyan
                Get-FileFromInternet -Uri $Win32ContentPrepToolUri -Destination $fileDestination
            }
        } 
        else {
            Write-LogAndHost -Message ("The 'PackageApps' parameter was not passed. Skipping downloading of the Win32 Content Prep Tool") -LogId $LogId -Severity 2 -ForegroundColor Yellow
        }
        #endRegion
        #region Connect_MgGraphCustom
        
        if ($CreateApps) {

            # Connect to Microsoft Graph based on the parameter set being used
            New-VerboseRegion -Message 'Connecting to the Microsoft Graph...' -ForegroundColor 'Gray'

            if ($PSBoundParameters.ContainsKey('TenantId') -and $PSBoundParameters.ContainsKey('ClientId')) {
                Write-LogAndHost -Message ("TenantId '{0}' and ClientId '{1}' are present" -f $TenantId, $ClientId) -LogId $LogId -ForegroundColor Cyan
                Write-LogAndHost -Message ("Parameter set used: {0}" -f $PSCmdlet.ParameterSetName) -LogId $LogId -ForegroundColor Cyan
                Write-LogAndHost -Message ("Required Scopes: {0}" -f ($RequiredScopes -join ', ')) -LogId $LogId -ForegroundColor Cyan
                Write-LogAndHost -Message "Testing connection to Microsoft Graph" -LogId $LogId -ForegroundColor Cyan

                if (-not (Test-MgConnection -RequiredScopes $RequiredScopes -TestScopes)) {
                    Write-LogAndHost -Message "No active Microsoft Graph connection found. Attempting to connect..." -LogId $LogId -ForegroundColor Cyan

                    # Attempt connection using available parameters
                    try {

                        # Create scopes string for logging
                        $scopesString = "($($RequiredScopes -join ', '))"

                        switch ($PSCmdlet.ParameterSetName) {
                            'ClientSecret' {
                                Write-LogAndHost -Message ("Connect-MgGraphCustom -TenantId '{0}' -ClientId '{1}' -ClientSecret '{2}'" -f $TenantId, $ClientId, 'ClientSecretObfuscated') -LogId $LogId -ForegroundColor Cyan
                                Connect-MgGraphCustom -TenantId $TenantId -ClientId $ClientId -ClientSecret $ClientSecret
                            }
                            'ClientCertificateThumbprint' {
                                Write-LogAndHost -Message ("Connect-MgGraphCustom -TenantId '{0}' -ClientId '{1}' -ClientCertificateThumbprint '{2}'" -f $TenantId, $ClientId, $ClientCertificate) -LogId $LogId
                                Connect-MgGraphCustom -TenantId $TenantId -ClientId $ClientId -ClientCertificateThumbprint $ClientCertificate
                            }
                            'UseDeviceAuthentication' {
                                Write-LogAndHost -Message ("Connect-MgGraphCustom -TenantId '{0}' -ClientId '{1}' -UseDeviceAuthentication -RequiredScopes {2}" -f $TenantId, $ClientId, $scopesString) -LogId $LogId -ForegroundColor Cyan
                                Connect-MgGraphCustom -TenantId $TenantId -ClientId $ClientId -UseDeviceAuthentication -RequiredScopes $RequiredScopes
                            }
                            'Interactive' {
                                Write-LogAndHost -Message ("Connect-MgGraphCustom -TenantId '{0}' -ClientId '{1}' -RequiredScopes {2}" -f $TenantId, $ClientId, $scopesString) -LogId $LogId -ForegroundColor Cyan
                                Connect-MgGraphCustom -TenantId $TenantId -ClientId $ClientId -RequiredScopes $RequiredScopes
                            }
                            default {
                                Write-LogAndHost -Message ("Unknown authentication method: {0}" -f $AuthenticationMethod) -LogId $LogId -Severity 3
                                break
                            }
                        }
                        
                        if (-not (Test-MgConnection -LogId $LogId -RequiredScopes $RequiredScopes -TestScopes)) {
                            Get-ScriptEnd -ErrorMessage "Failed to connect to Microsoft Graph." -LogId $LogId
                        }
                    }
                    catch {
                        Get-ScriptEnd -ErrorMessage $_.Exception.Message -LogId $LogId
                    }
                }
            }
            else {

                # If TenantId and ClientId are not passed, we cannot connect to Microsoft Graph and leverage the CreateApps parameter
                Get-ScriptEnd -ErrorMessage "TenantId and ClientId are required to connect to Microsoft Graph." -LogId $LogId
            }
        }
        #endregion
        #region Display_Application_Results
        New-VerboseRegion -Message 'Filtering application results' -ForegroundColor 'Gray'

        # Build a hash table of switch parameters to pass to the Get-AppList function
        $paramsToPassApp = @{}
        if ($ExcludePMPC) {
            $paramsToPassApp.Add('ExcludePMPC', $true) 
            Write-LogAndHost -Message "The ExcludePMPC parameter was passed. Ignoring all PMPC created applications" -LogId $LogId -Severity 2 -ForegroundColor Cyan
        }
        if ($ExcludeFilter) {
            $paramsToPassApp.Add('ExcludeFilter', $ExcludeFilter) 
            Write-LogAndHost -Message ("The 'ExcludeFilter' parameter was passed. Ignoring applications that match '{0}'" -f $ExcludeFilter) -LogId $LogId -Severity 2 -ForegroundColor Cyan
        }
        if ($NoOGV) {
            $paramsToPassApp.Add('NoOGV', $true) 
            Write-LogAndHost -Message "The 'NoOgv' parameter was passed. Suppressing Out-GridView" -LogId $LogId -Severity 2 -ForegroundColor Cyan
        }

        Write-LogAndHost -Message ("Running function 'Get-AppList' -AppName '{0}'" -f $AppName) -LogId $LogId -ForegroundColor Cyan

        $applicationName = Get-AppList -AppName $AppName @paramsToPassApp
 
        # ApplicationName(s) returned from the Get-AppList function
        if ($applicationName) {
            Write-Log -Message "The Win32App Migration Tool will process the following applications:" -LogId $LogId
            Write-Host "The Win32App Migration Tool will process the following applications:" -ForegroundColor Cyan
        
            foreach ($application in $ApplicationName) {
                Write-Log -Message ("Id = '{0}', Name = '{1}'" -f $application.Id, $application.LocalizedDisplayName) -LogId $LogId
                Write-Host ("Id = '{0}', Name = '{1}'" -f $application.Id, $application.LocalizedDisplayName) -ForegroundColor Green
            }
        }
        else {
            Write-Log -Message ("There were no applications found that match the crieria '{0}' or the Out-GrideView was closed with no selection made. Cannot continue" -f $AppName) -LogId $LogId -Severity 3
            Write-Warning -Message ("There were no applications found that match the crieria '{0}' or the Out-GrideView was closed with no selection made. Cannot continue" -f $AppName)
            Get-ScriptEnd
        }
        
        #endRegion

        #region Get_App_Details
        New-VerboseRegion -Message 'Getting application details' -ForegroundColor 'Gray'

        # Calling function to grab application details
        Write-Log -Message "Calling 'Get-AppInfo' function to grab application details" -LogId $LogId
        Write-Host "Calling 'Get-AppInfo' function to grab application details" -ForegroundColor Cyan

        $app_Array = Get-AppInfo -ApplicationName $applicationName
        #endregion

        #region Get_DeploymentType_Details
        New-VerboseRegion -Message 'Getting deployment type details' -ForegroundColor 'Gray'

        # Calling function to grab deployment types details
        Write-Log -Message "Calling 'Get-DeploymentTypeInfo' function to grab deployment type details" -LogId $LogId
        Write-Host "Calling 'Get-DeploymentTypeInfo' function to grab deployment type details" -ForegroundColor Cyan
    
        $deploymentTypes_Array = foreach ($app in $app_Array) { Get-DeploymentTypeInfo -ApplicationId $app.Id }
        #endregion

        #region Get_DeploymentType_Content
        New-VerboseRegion -Message 'Getting deployment type content information' -ForegroundColor 'Gray'
  
        # Calling function to grab deployment type content information
        Write-LogAndHost -Message "Calling 'Get-ContentFiles' function to grab the deployment type content" -LogId $LogId -ForegroundColor Cyan
            
        $content_Array = foreach ($deploymentType in $deploymentTypes_Array) { 
    
            # Build a hash table of parameters to pass to the Get-ContentFiles function
            $paramsToPassContent = @{}
    
            if ($deploymentType.InstallContent) { $paramsToPassContent.Add('InstallContent', $deploymentType.InstallContent) }
            $paramsToPassContent.Add('UninstallSetting', $deploymentType.UninstallSetting)

            if ($deploymentType.UninstallContent) { $paramsToPassContent.Add('UninstallContent', $deploymentType.UninstallContent) }
            $paramsToPassContent.Add('ApplicationId', $deploymentType.Application_Id)
            $paramsToPassContent.Add('ApplicationName', $deploymentType.ApplicationName)
            $paramsToPassContent.Add('DeploymentTypeLogicalName', $deploymentType.LogicalName)
            $paramsToPassContent.Add('DeploymentTypeName', $deploymentType.Name)
            $paramsToPassContent.Add('InstallCommandLine', $deploymentType.InstallCommandLine)

            # If we have content, call the Get-ContentInfo function
            if ($deploymentType.InstallContent -or $deploymentType.UninstallContent) { Get-ContentInfo @paramsToPassContent }
        }

        # If $DownloadContent was passed, download content to the working folder
        New-VerboseRegion -Message 'Copying content files' -ForegroundColor 'Gray'

        if ($DownloadContent) {
            Write-LogAndHost -Message "The 'DownloadContent' parameter was passed" -LogId $LogId -ForegroundColor Cyan

            foreach ($content in $content_Array) {
                Get-ContentFiles -Source $content.Install_Source -Destination $content.Install_Destination

                # If the uninstall content is different to the install content, copy that too
                if ($content.Uninstall_Setting -eq 'Different') {
                    Get-ContentFiles -Source $content.Uninstall_Source -Destination $content.Uninstall_Destination -Flags 'UninstallDifferent'
                }
            }  
        }
        else {
            Write-LogAndHost -Message "The 'DownloadContent' parameter was not passed. Skipping content download" -LogId $LogId -Severity 2
        }
        #endregion
        #region Exporting_Csv data

        # Export $DeploymentTypes to CSV for reference
        New-VerboseRegion -Message 'Exporting collected data to Csv' -ForegroundColor 'Gray'
        $detailsFolder = (Join-Path -Path $workingFolder_Root -ChildPath 'Details')
        Write-LogAndHost -Message ("Destination folder will be '{0}\Details" -f $workingFolder_Root) -LogId $LogId -Severity 2 -ForegroundColor Cyan

        # Export application information to CSV for reference
        Export-CsvDetails -Name 'Applications' -Data $app_Array -Path $detailsFolder

        # Export deployment type information to CSV for reference
        Export-CsvDetails -Name 'DeploymentTypes' -Data $deploymentTypes_Array -Path $detailsFolder

        # Export content information to CSV for reference
        if ($DownloadContent) {
            Export-CsvDetails -Name 'Content' -Data $content_Array -Path $detailsFolder
        }
        #endregion
        #region Exporting_Logos

        # Export icon(s) for the applications
        New-VerboseRegion -Message 'Exporting icon(s)' -ForegroundColor 'Gray'

        if ($ExportIcon) {
            Write-Log -Message "The 'ExportIcon' parameter passed" -LogId $LogId

            foreach ($applicationIcon in $app_Array) {

                if ([string]::IsNullOrWhiteSpace($applicationIcon.IconData)) {
                    Write-LogAndHost -Message ("No icon data found for '{0}'. Skipping icon export" -f $applicationIcon.Name) -LogId $LogId -Severity 2
                }
                else {
                    Write-LogAndHost -Message ("Exporting icon for '{0}' to '{1}'" -f $applicationIcon.Name, $applicationIcon.IconPath) -Logid $LogId -ForegroundColor Cyan
                
                    # Export the icon to disk
                    Export-Icon -AppName $applicationIcon.Name -IconPath $applicationIcon.IconPath -IconData $applicationIcon.IconData
                }
            }
        }
        else {
            Write-LogAndHost -Message "The 'ExportIcon' parameter was not passed. Skipping icon export" -LogId $LogId -Severity 2
        }
        #endregion
        #region Package_Apps

        if ($PackageApps) {

            # If the $PackageApps parameter was passed. Use the Win32Content Prep Tool to build Intune.win files
            New-VerboseRegion -Message 'Creating intunewin file(s)' -ForegroundColor 'Gray'
            Write-LogAndHost -Message "The 'PackageApps' Parameter was passed" -LogId $LogId -ForegroundColor Cyan
            
            foreach ($content in $content_Array) {

                Write-LogAndHost -Message ("Working on application '{0}'..." -f $content.Application_Name) -LogId $LogId -ForegroundColor Cyan

                # Create the Win32app folder for the .intunewin files
                New-FolderToCreate -Root "$workingFolder_Root\Win32Apps" -FolderNames $content.Win32app_Destination
        
                # Create intunewin files
                Write-LogAndHost -Message ("Creating intunewin file for the deployment type '{0}' for app '{1}'" -f $content.DeploymentType_Name, $content.Application_Name) -LogId $LogId -ForegroundColor Cyan
            
                # Build parameters to splat at the New-IntuneWin function
                $paramsToPassIntuneWin = @{}

                # If DownloadContent switch is not passed we will use content from the Configmgr source folder
                if ($DownloadContent) {
                    $paramsToPassIntuneWin.Add('ContentFolder', $content.Install_Destination)
                }
                else {
                    $paramsToPassIntuneWin.Add('ContentFolder', $content.Install_Source)
                }

                $paramsToPassIntuneWin.Add('OutputFolder', (Join-Path -Path "$workingFolder_Root\Win32Apps" -ChildPath $content.Win32app_Destination))
                $paramsToPassIntuneWin.Add('SetupFile', $content.Install_CommandLine)

                if ($OverrideIntuneWin32FileName) { 
                    $paramsToPassIntuneWin.Add('OverrideIntuneWin32FileName', $OverrideIntuneWin32FileName) 
                }

                # Create the .intunewin file
                New-IntuneWin @paramsToPassIntuneWin
            }
        }
        else {
            Write-LogAndHost -Message "The 'PackageApps' parameter was not passed. Intunewin files will not be created" -LogId $LogId -Severity 2
        }
        #endRegion
        #region Create Intune Win32 app JSON body

        if ($CreateApps -and $PackageApps) {

            # If the $CreateApps parameter was passed. Start creating the Win32 apps in Intune
            Write-LogAndHost -Message "The 'CreateApps' Parameter was passed" -LogId $LogId -ForegroundColor Cyan
            New-VerboseRegion -Message 'Creating Win32 app JSON body' -ForegroundColor 'Gray'

            foreach ($app in $app_array) {
        
                foreach ($deploymentType in $deploymentTypes_Array | Where-Object { $_.Application_Logicalname -eq $app.LogicalName }) {

                    foreach ($content in $content_Array | Where-Object { $_.DeploymentType_LogicalName -eq $deploymentType.LogicalName }) {

                        Write-LogAndHost -Message ("Working on application '{0}'..." -f $app.Name) -LogId $LogId -ForegroundColor Cyan

                        # Create the Win32app folder for the JSON files if it doesn't exist
                        if (-not (Test-Path -Path "$workingFolder_Root\Win32Apps") ) {
                            New-FolderToCreate -Root "$workingFolder_Root\Win32Apps" -FolderNames $content.Win32app_Destination
                        }

                        $PathforWin32AppBodyJSON = Join-Path -Path "$workingFolder_Root\Win32Apps" -ChildPath $content.Win32app_Destination

                        # Get the intunewin meta data
                        try {
                            $intuneWinSetupFilePath = Get-ChildItem -Path $PathforWin32AppBodyJSON -Filter "*.intunewin" -Recurse | Select-Object -First 1 -ExpandProperty FullName
                            
                            if ($intuneWinSetupFilePath) {
                                Write-LogAndHost -Message ("Found the .intunewin file at '{0}'" -f $intuneWinSetupFilePath) -LogId $LogId -ForegroundColor Green

                                # Get the intunewin file information
                                $intuneWinInfo = Get-IntuneWinInfo -SetupFile $intuneWinSetupFilePath
                            }
                            else {
                                Write-LogAndHost -Message ("Failed to get the .intunewin file from '{0}'" -f $PathforWin32AppBodyJSON) -LogId $LogId -Severity 3
                                break
                            }
                        }
                        catch {
                            Write-LogAndHost -Message ("Failed to get the .intunewin file from '{0}'" -f $PathforWin32AppBodyJSON) -LogId $LogId -Severity 3
                            break
                        }

                        # Create Win32 app body
                        Write-LogAndHost -Message ("Creating Win32 app body for the deployment type '{0}' for app '{1}'" -f $deploymentType.Name, $deploymentType.ApplicationName) -LogId $LogId -ForegroundColor Cyan

                        # Build parameters to splat at the New-IntuneWinFramework function

                        # Deal with empty values that are required
                        $Description = if ($app.Description) { $app.Description } else { $app.Name }

                        # Body for the Win32 app
                        $paramsToPassWin32App = @{}
                        $paramsToPassWin32App.Add('Name', $app.Name)
                        $paramsToPassWin32App.Add('Description', $Description)
                        $publisher = if ([string]::IsNullOrWhiteSpace($app.Publisher)) { "Not Specified" } else { $app.Publisher }
                        $paramsToPassWin32App.Add('Publisher', $publisher)
                        $paramsToPassWin32App.Add('AppVersion', $app.Version)
                        $paramsToPassWin32App.Add('InformationURL', $app.InfoURL)
                        $paramsToPassWin32App.Add('PrivacyURL', $app.PrivacyURL)
                        $paramsToPassWin32App.Add('Notes', $Win32AppNotes)
                        $paramsToPassWin32App.Add('LargeIcon', $app.IconData)
                        $paramsToPassWin32App.Add('Path', $PathforWin32AppBodyJSON)
                        $paramsToPassWin32App.Add('FileName', $intuneWinInfo.FileName)
                        $paramsToPassWin32App.Add('SetupFile', $intuneWinInfo.SetupFile)
                        $paramsToPassWin32App.Add('InstallCommandLine', $deploymentType.InstallCommandLine)
                        $paramsToPassWin32App.Add('UninstallCommandLine', $deploymentType.UninstallCommandLine)
                        $paramsToPassWin32App.Add('InstallExperience', $deploymentType.ExecutionContext)

                        if ($PSBoundParameters.ContainsKey('AllowAvailableUninstall')) {
                            $paramsToPassWin32App.Add('AllowAvailableUninstall', $AllowAvailableUninstall)
                        }
                        else {
                            $paramsToPassWin32App.Add('AllowAvailableUninstall', $false)
                        }

                        # Detection method (rules) for the Win32 app
                        if ($deploymentType.DetectionMethodJsonFile) {
                            try {
                                $jsonBlob = Get-Content -Path $deploymentType.DetectionMethodJsonFile -Raw
                                $paramsToPassWin32App.Add('DetectionMethodJson', ($jsonBlob))
                            }
                            catch {
                                Write-LogAndHost -Message ("Failed to read the JSON file '{0}'" -f $deploymentType.DetectionMethodJsonFile) -LogId $LogId -Severity 3
                            }
                        }
                        elseif ($deploymentType.DetectionTypeScriptType) {
                            $bytes = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((Get-Content -Path "$($deploymentType.DetectionMethodScriptFile)" -Raw -Encoding UTF8)))
                            $paramsToPassWin32App.Add('DetectionScript', $bytes)
                        }
                        else {
                            Write-LogAndHost -Message ("No detection method found for '{0}'" -f $app.Name) -LogId $LogId -Severity 3
                        }

                        if ($deploymentType.MaxExecuteTime) {
                            $paramsToPassWin32App.Add('MaxExecutionTime', $deploymentType.MaxExecuteTime)
                            
                        }
                    }

                    # Create the JSON body file
                    $newIntuneJson = New-IntuneWinFramework @paramsToPassWin32App

                    if ($newIntuneJson) {

                        Write-LogAndHost -Message "We have a JSON body for the Win32 app" -LogId $LogId -ForegroundColor Green
                        New-VerboseRegion -Message 'Creating Win32 app in Intune' -ForegroundColor 'Gray'
            
                        # Create the Win32 app in Intune
                        $response = Invoke-MgGraphRequestCustom -Resource 'deviceAppManagement/mobileApps' -Method Post -Body $newIntuneJson

                        if ($response.id) {
                            Write-LogAndHost -Message ("Successfully created the Win32 app '{0}' in Intune for deployment type '{1}'. AppId is '{2}'" -f $app.Name, $deploymentType.Name, $response.id) -LogId $LogId -ForegroundColor Green

                            # Create the content request for the Win32 app
                            try {

                                # Get Encryption information to use for the commit later
                                $encryptionInfo = Get-IntuneWinEncryptionInfo -FilePath $intuneWinSetupFilePath
                                
                                # Get the encrypted size of the intunewin file
                                $sizeEncrypted = (Get-Item -Path $encryptionInfo.intuneWinPath).Length
                                Write-LogAndHost -Message ("The size of the encrypted intunewin file is '{0}'" -f $sizeEncrypted) -LogId $LogId -ForegroundColor Green
                                Write-LogAndHost -Message "Building JSON for the content request" -LogId $LogId -ForegroundColor Cyan

                                # Get the content request file name and unencrypted size
                                $contentRequestFileName = $encryptionInfo.contentApplicationInfo | ConvertFrom-Json | Select-Object -ExpandProperty  fileName
                                $contentRequestSizeUnencrypted = $encryptionInfo.contentApplicationInfo | ConvertFrom-Json | Select-Object -ExpandProperty UnencryptedContentSize
                                
                                $contentRequest = New-IntuneWinContentRequest -Name $contentRequestFileName  -SizeUnencrypted $contentRequestSizeUnencrypted -SizeEncrypted $sizeEncrypted -IsDependency $false
                            }
                            catch {
                                Write-LogAndHost -Message ("Failed to create the content request for the Win32 app '{0}' in Intune for deployment type '{1}'" -f $app.Name, $deploymentType.Name) -LogId $LogId -Severity 3
                            }

                            # Check content version request was successful
                            if ($contentRequest) {

                                Write-LogAndHost -Message ("Successfully created the content version request for the Win32 app '{0}' in Intune for deployment type '{1}'. " -f $AppName, $DeploymentTypeName) -LogId $LogId -ForegroundColor Green

                                # Call the Get-SasUri function to get the Sas Urifor the content
                                $sasUri = Get-SasUri -Win32AppId $response.id -ContentRequest ($contentRequest | ConvertTo-Json -Depth 5 -Compress)
                                
                                if ($sasUri.contentReady -and $sasUri.contentVersion -and $sasUri.contentRequestId) {
                                   
                                    # Attempt to upload the content to the Sas Uri
                                    $uploadSuccess = Invoke-StorageUpload -Uri $sasUri.contentReady.azureStorageUri -FilePath $encryptionInfo.intuneWinPath -FileSize $sizeEncrypted -ContentVersion $sasUri.contentVersion -ContentRequestId $sasUri.contentRequestId -Win32AppId $response.id -ContentRequest $contentRequest
                                }
                                if ($uploadSuccess) {
    
                                    Write-LogAndHost -Message ("Committing the encryption information for the Win32 app with Id '{0}'" -f $response.id) -LogId $LogId -ForegroundColor Cyan

                                    # Commit the content to the Win32 app
                                    $commitResponse = Invoke-IntuneContentCommit -Win32AppId $response.id -EncryptionInfo $encryptionInfo.encryptionDetails -ContentVersion $sasUri.contentVersion -ContentRequestId $sasUri.contentRequestId

                                    if ($commitResponse) {
                                        Write-LogAndHost -Message ("Migration Complete. App name '{0}' migrated successfully to Intune with App Id '{1}'" -f $app.Name, $response.id) -LogId $LogId -ForegroundColor Green
                                    }
                                    else {
                                        Write-LogAndHost -Message ("Migration failure: Unable to update the Win32 app '{0}' with the content locations in Intune for deployment type '{1}'" -f $app.Name, $deploymentType.Name) -LogId $LogId -Severity 3
                                    }
                                }
                                else {
                                    Write-LogAndHost -Message ("Failed to upload the content for the Win32 app '{0}' in Intune for deployment type '{1}'" -f $app.Name, $deploymentType.Name) -LogId $LogId -Severity 3
                                }
                            }
                            else {
                                Write-LogAndHost -Message ("Failed to create the content version request for the Win32 app '{0}' in Intune for deployment type '{1}'" -f $app.Name, $deploymentType.Name) -LogId $LogId -Severity 3
                            
                            }
                        }
                        else {
                            Write-LogAndHost -Message ("Failed to create the Win32 app '{0}' in Intune for deployment type '{1}'" -f $app.Name, $deploymentType.Name) -LogId $LogId -Severity 3
                        }
                    }
                    else {
                        Write-LogAndHost -Message ("Failed to create the JSON body for '{0}'" -f $app.Name) -LogId $LogId -Severity 3
                    }          
                }
            }
            #endregion
            Get-ScriptEnd
        }
    }
}