ApplicationFactoryService.psm1

#Region '.\Private\Add-AppFactoryApplicationBlockingProcess.ps1' 0
function Add-AppFactoryApplicationBlockingProcess{
  [cmdletbinding()]
  [OutputType([System.Collections.Generic.List[String[]]])]
  param(
    [Parameter()][switch]$interactive,   
    [Parameter()][ValidateNotNullOrEmpty()][string]$blockingProcess,
    [Parameter()][ValidateNotNullOrEmpty()][int]$deferCount = 0,
    [Parameter()][string[]]$customScript,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  $ApplicationScriptLines = [System.Collections.Generic.List[String[]]]@()
  $processes = $blockingProcess -split ","
  if($interactive.IsPresent){
    $ApplicationScriptLines.Add(("`$processExist = `$false",2)) | Out-Null
    foreach ($item in $processes) {
      $ApplicationScriptLines.Add(("if(Get-Process -Name `"$($item)`" -ErrorAction SilentlyContinue){`$processExist = `$true}",2)) | Out-Null
    }
    $ApplicationScriptLines.Add(("if ([Environment]::UserInteractive -and `$processExist) {",2)) | Out-Null
    $ApplicationScriptLines.Add(("`$params = @{",3)) | Out-Null
    $ApplicationScriptLines.Add(("`"CloseApps`" = `"$($blockingProcess)`"",4)) | Out-Null
    $ApplicationScriptLines.Add(("`"PersistPrompt`" = `$true",4)) | Out-Null
    if ($deferCount -gt 0) {
      $ApplicationScriptLines.Add(("`"AllowDefer`" = `$true",4)) | Out-Null
      $ApplicationScriptLines.Add(("`"DeferTimes`" = $($deferCount)",4)) | Out-Null
    }
    $ApplicationScriptLines.Add(("}",3)) | Out-Null
    $ApplicationScriptLines.Add(("Show-InstallationWelcome @params",3)) | Out-Null
    $ApplicationScriptLines.Add(("}",2)) | Out-Null    
    $ApplicationScriptLines.Add(("else {",2)) | Out-Null
    foreach ($item in $processes) {
      $ApplicationScriptLines.Add(("`Get-Process -Name `"$($item)`" | Stop-Process -Force",3)) | Out-Null
    }
    $ApplicationScriptLines.Add(("}",2)) | Out-Null      
  }
  else{
    foreach($item in $processes){
      $ApplicationScriptLines.Add(("Get-Process -Name `"$($item)`" | Stop-Process -Force",2)) | Out-Null
    }
  }
  return @(,$ApplicationScriptLines)
}
#EndRegion '.\Private\Add-AppFactoryApplicationBlockingProcess.ps1' 42
#Region '.\Private\Add-AppFactoryApplicationScriptEXE.ps1' 0
function Add-AppFactoryApplicationScriptEXE{
  [cmdletbinding()]
  [OutputType([System.Collections.Generic.List[String[]]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][Hashtable]$ApplicationInstallLines,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$installer,
    [Parameter()][String]$directory,
    [Parameter()][string]$Parameters = $null,
    [Parameter()][string]$ignoreExit = $null,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  $ApplicationScriptLines = [System.Collections.Generic.List[String[]]]@()
  if($directory -ne ""){
    $executePath = "`"`$($($directory))\`$(`$installer)`""
  }
  else{
    $executePath = "`"`$(`$installer)`""
  }
  $execute = "Execute-Process -Path $($executePath) -Parameters `"`$(`$Parameters)`""
  if($ignoreExit){
    $execute = "$($execute) -IgnoreExitCodes `"$($ignoreExit)`""
  }
  $ApplicationScriptLines.Add(("`$installer = `"$($installer)`"", 2)) | Out-Null
  $ApplicationScriptLines.Add(("`$Parameters = `"$($Parameters)`"",2)) | Out-Null
  $ApplicationScriptLines.Add(($execute, 2)) | Out-Null
  return @(,$ApplicationScriptLines)
}
#EndRegion '.\Private\Add-AppFactoryApplicationScriptEXE.ps1' 28
#Region '.\Private\Add-AppFactoryApplicationScriptMSI.ps1' 0
function Add-AppFactoryApplicationScriptMSI{
  [cmdletbinding()]
  [OutputType([System.Collections.Generic.List[String[]]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][Hashtable]$ApplicationInstallLines,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$installer,
    [Parameter()][String]$directory,
    [Parameter()][string]$Parameters = $null,
    [Parameter()][string]$mst = $null,
    [Parameter()][ValidateSet("Install", "Uninstall")][string]$msiAction = "Install",
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  $ApplicationScriptLines = [System.Collections.Generic.List[String[]]]@()
  if($directory -ne ""){
    $executePath = "`"`$($($directory))\`$(`$installer)`""
  }
  else{
    $executePath = "`"`$(`$installer)`""
  }  
  $execute = "Execute-MSI -Action `"$($msiAction)`" -Path $($executePath) -Parameters `"/qn REBOOT=ReallySuppress`""
  $ApplicationScriptLines.Add(("`$installer = `"$($installer)`"", 2)) | Out-Null
  if ($mst) {
    $ApplicationScriptLines.Add(("`$mstInstall = `"$($mst)`"", 2)) | Out-Null
    $execute = "$($execute) -Transform `"`$($($directory))\`$(`$mstInstall)`""
  }
  if ($Parameters) {
    $ApplicationScriptLines.Add(("`$Parameters = `"$($Parameters)`"", 2)) | Out-Null
    $execute = "$($execute) -AddParameter `$parameters"
  } 
  $ApplicationScriptLines.Add(($execute, 2)) | Out-Null
  return @(,$ApplicationScriptLines)
}
#EndRegion '.\Private\Add-AppFactoryApplicationScriptMSI.ps1' 33
#Region '.\Private\Add-AppFactoryAppWIM.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to generate the lines to enter into deploy-application.ps1 for WIM based installs.
  .PARAMETER section
  If this for the start or end of the WIM process that determines if it is mounting or dismounting
  .PARAMETER ApplicationInstallLines
  The current lines that are meant to be injected into the file, so that it adds to the current object
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
#>

function Add-AppFactoryAppWIM{
  [cmdletbinding()]
  [OutputType([System.Collections.Generic.List[String[]]])]
  param(
    [Parameter(Mandatory = $true)][ValidateSet("start","end")][string]$section,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application
  )
  $ApplicationScriptLines = [System.Collections.Generic.List[String[]]]@()
  # If this to mount the WIM add the following lines
  if($section -eq "start"){
    $ApplicationScriptLines.Add(("`$mountPath = `"C:\AFS\$($application.AppFolderName)`"",2))  | Out-Null
    $ApplicationScriptLines.Add(("`$wimFile = Get-Childitem -Path `"`$(`$dirFiles)`" -Filter `"*.wim`"",2))  | Out-Null
    $ApplicationScriptLines.Add(("`$wimPath = Join-Path `$dirFiles -ChildPath `$wimFile",2))  | Out-Null
    $ApplicationScriptLines.Add(("try{",2))  | Out-Null
    $ApplicationScriptLines.Add(("[void](New-Item -Path `$mountPath -ItemType Directory -ErrorAction SilentlyContinue)",3))  | Out-Null
    $ApplicationScriptLines.Add(("Mount-WindowsImage -ImagePath `$wimPath -Index 1 -Path `$mountPath",3))  | Out-Null
    $ApplicationScriptLines.Add(("}",2))  | Out-Null
    $ApplicationScriptLines.Add(("catch{",2))  | Out-Null
    $ApplicationScriptLines.Add(("Write-Host `"ERROR: Encountered an issue mounting the .wim. Exiting Script now.`"",3))  | Out-Null
    $ApplicationScriptLines.Add(("Write-Host `"Error Message: `$_`"",3))  | Out-Null
    $ApplicationScriptLines.Add(("}",2))  | Out-Null
    $ApplicationScriptLines.Add(("",2))  | Out-Null
  }
  else{
    $ApplicationScriptLines.Add(("",2))  | Out-Null
    $ApplicationScriptLines.Add(("Start-Sleep -Seconds 60",2))  | Out-Null
    $ApplicationScriptLines.Add(("try{",2))  | Out-Null
    $ApplicationScriptLines.Add(("Dismount-WindowsImage -Path `$MountPath -Discard",3))  | Out-Null
    $ApplicationScriptLines.Add(("Remove-Item -Path `$mountPath -Force",3))  | Out-Null
    $ApplicationScriptLines.Add(("}",2))  | Out-Null
    $ApplicationScriptLines.Add(("catch{",2))  | Out-Null
    $ApplicationScriptLines.Add(("#Failed to Dismount normally. Setting up a scheduled task to unmount after next reboot (exit code 3010)",3))  | Out-Null
    $ApplicationScriptLines.Add(("Write-Host `"ERROR: Attempting to create scheduled task CleanupWIM to dismount image at next startup`"",3))  | Out-Null
    $ApplicationScriptLines.Add(("Write-Host `"Error Message: `$_`"",3))  | Out-Null
    $ApplicationScriptLines.Add(("`$STAction = New-ScheduledTaskAction ``",3))  | Out-Null
    $ApplicationScriptLines.Add(("-Execute 'Powershell.exe' ``",4))  | Out-Null
    $ApplicationScriptLines.Add(("-Argument '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -command `"& {Get-WindowsImage -Mounted | Where-Object {`$_.MountStatus -eq ''Invalid''} | ForEach-Object {`$_ | Dismount-WindowsImage -Discard -ErrorVariable wimerr; if ([bool]`$wimerr) {`$errflag = `$true}}; If (-not `$errflag) {Clear-WindowsCorruptMountPoint; Unregister-ScheduledTask -TaskName ''CleanupWIM'' -Confirm:`$false}}`"'",4))  | Out-Null
    $ApplicationScriptLines.Add(("`$STTrigger = New-ScheduledTaskTrigger -AtStartup",4))  | Out-Null
    $ApplicationScriptLines.Add(("Register-ScheduledTask ``",3))  | Out-Null
    $ApplicationScriptLines.Add(("-Action `$STAction ``",4))  | Out-Null
    $ApplicationScriptLines.Add(("-Trigger `$STTrigger ``",4))  | Out-Null
    $ApplicationScriptLines.Add(("-TaskName `"CleanupWIM`" ``",4))  | Out-Null
    $ApplicationScriptLines.Add(("-Description `"Clean up WIM Mount points that failed to dismount`" ``",4))  | Out-Null
    $ApplicationScriptLines.Add(("-User `"NT AUTHORITY\SYSTEM`" ``",4))  | Out-Null
    $ApplicationScriptLines.Add(("-RunLevel Highest ``",4))  | Out-Null
    $ApplicationScriptLines.Add(("-Force ",4))  | Out-Null
    $ApplicationScriptLines.Add(("}",2))  | Out-Null      
  }
  return @(,$ApplicationScriptLines)
}
#EndRegion '.\Private\Add-AppFactoryAppWIM.ps1' 61
#Region '.\Private\Add-AppFactoryIntuneDeployment.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to upload the required items to the Azure Storage for the deployment files
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER appStorageContext
  The azure storage context that has permissions to upload to the Azure Storage Account
  .PARAMETER ApplicationInstallLines
  The name of the public storage blob that we will use if the application is free for any organization
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Add-AppFactoryIntuneDeployment {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$appStorageContext,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$AppFactoryPublicFolder,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Generate the path for the details for the application and then read the JSON file
  $AppDataFile = Join-Path -Path $application.AppPublishFolderPath -ChildPath "App.json"
  $AppData = Get-Content -Path $AppDataFile | ConvertFrom-Json
  $ScriptDataFile = Join-Path -Path $application.AppPublishFolderPath -ChildPath "Scripts" -AdditionalChildPath $AppData.DetectionRule.ScriptFile
  $AppIconFile = Join-Path -Path $application.AppPublishFolderPath -ChildPath $AppData.PackageInformation.IconFile
  # Check what Azure Storage Containers we should be uploading the files to
  $containerUploads = [System.Collections.Generic.List[PSCustomObject]]@()
  if ((($application.PublishTo.getType()).BaseType.Name -eq "Array" -and $application.publishTo.Count -gt 0) -or (($application.PublishTo.getType()).BaseType.Name -eq "Object" -and $application.publishTo.length -gt 1)) {
    foreach ($org in $application.PublishTo) {
      $containerUploads.Add($org) | Out-Null
    }    
  }
  else {
    $containerUploads.Add($AppFactoryPublicFolder) | Out-Null
  }
  $appUploads = [PSCustomObject]@(
    @{
      "File" = $AppDataFile
      "Blob" = "$($application.GUID)/$($application.AppSetupVersion)/App.json"
    },
    @{
      "File" = $application.IntunePackage
      "Blob" = "$($application.GUID)/$($application.AppSetupVersion)/$($application.IntuneAppName).intunewin"
    },
    @{
      "File" = $AppIconFile
      "Blob" = "$($application.GUID)/$($application.AppSetupVersion)/$($AppData.PackageInformation.IconFile)"
    },
    @{
      "File" = $ScriptDataFile
      "Blob" = "$($application.GUID)/$($application.AppSetupVersion)/detection.ps1"
    }       
  )
  # Process the uploads for each of the files as required to each container that is required
  foreach ($container in $containerUploads) {
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Uploading files to storage" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    }
    foreach ($upload in $appUploads) {
      if (Test-Path $upload.File) {
        try {
          if ($script:AppFactoryLogging) {
            Write-PSFMessage -Message "[$($application.IntuneAppName)] Uploading <c='green'>$($upload.File)</c> to <c='green'>$($container)</c> container" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
          }
          Set-AzStorageBlobContent @upload -Container $container -Context $appStorageContext -Force -ErrorAction Stop | Out-Null
        }
        catch {
          throw "[$($application.IntuneAppName)] Unable able to upload file: $($_.Exception.Message)"
        }        
      }
    }
  }
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Updating versions information" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  # Add a item to the object that we can check for to see if it was actually sent
  Set-AppFactoryAppConfiguration -GUID $application.GUID -version $application.AppSetupVersion
  $application | Add-Member -MemberType NoteProperty -Name 'Updated' -Value $true -Force  
  return $application
}
#EndRegion '.\Private\Add-AppFactoryIntuneDeployment.ps1' 82
#Region '.\Private\Connect-AppFactoryAzureStorage.ps1' 0
function Connect-AppFactoryAzureStorage {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$storageContainer,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][securestring]$storageSecret,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose" 
  )
  begin {
    # Generate variables to connect to azure storage
    $storageVars = @{
      "StorageAccountName" = $storageContainer
      "StorageAccountKey"  = ConvertFrom-SecureString $storageSecret -AsPlainText
    }    
  }
  process {
    try {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "Creating Azure Storage Context for <c='green'>$($storageContainer)</c>" -Level $LogLevel -Tag "Storage", "$($storageContainer)" -Target "Application Factory Service"
      }
      $storageAccountContext = New-AzStorageContext @storageVars
    }
    catch {
      Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Storage" -Target "Application Factory Service"
      throw $_
    }
  }
  end {
    return $storageAccountContext
  }
}
#EndRegion '.\Private\Connect-AppFactoryAzureStorage.ps1' 32
#Region '.\Private\Expand-7ZIpFile.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to use the 7zip portable exe to extract the files
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER applicationName
  The name of the application that we are working with
  .PARAMETER path
  The path to the 7-zip file
  .PARAMETER supportFiles
  The path to the support files folder
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Expand-7ZIpFile{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$applicationName,
    [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$path,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$supportFiles,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose" 
  )  
  # 7Zip Exe
  $7ZipPath = Join-Path -Path $supportFiles -ChildPath "7zr.exe"
  # 7Zip File
  $7ZipFile = Join-Path -Path $path -ChildPath $application.appSetupName
  # Extract Directory
  $ExtractDirectory = Join-Path -Path $path -ChildPath $applicationName
  # Extract Files
  $vars = @{
    "FilePath" = $7zipPath
    "ArgumentList" = "x `"$($7ZipFile)`" -aoa -o`"$($ExtractDirectory)`""
    "Wait" = $true
  }  
  Start-Process @vars
  # Get list of files
  $files = Get-ChildItem -Path (Join-Path -Path $path -ChildPath $applicationName) | Where-Object {$_.Name -notmatch "^.*intunewin$"}
  foreach($file in $files){
    $filepath = $file.FullName
    if($file.PSIsContainer){
      $destination = Split-Path $file.Parent
    }
    else{
      $destination = Split-Path $file.Directory
    }
    try{
      Move-Item -Path $filepath -Destination $destination -Force -ErrorAction Stop
    }
    catch{
      throw "[$($application.IntuneAppName)] Unable to move app file: $($_.Exception.Message)"
    }
  }
  try{
    Remove-Item -Path $ExtractDirectory -Force -Recurse -Confirm:$false
  }
  catch{
    throw "[$($application.IntuneAppName)] Unable to cleanup $($_.Exception.Message)"
  }
}
#EndRegion '.\Private\Expand-7ZIpFile.ps1' 61
#Region '.\Private\Get-AppFactoryAppVersionNormalization.ps1' 0
function Get-AppFactoryAppVersionNormalization {
  [CmdletBinding()]
  [OutputType([System.Version])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$version
  )
  # Remove Any Hypons and Underscopes and replace with dots
  $version = ($version -replace "-",".") -replace "_","."
  # Remove any plus signs and replace with nothing
  $version = $version -replace "\+"
  # Replace any alphanumerica and replace with nothing
  $version = $version -replace "[a-zA-Z]"
  # Get the length as too long of a version number will cause issues
  $verlength = ([regex]::Matches($version, "\." )).count
  # If the length is greater than 3 then we will only look at the first four items
  if($verlength -gt 3){
    $items = $version -split "\."
    $version =  ($items[0..3] -join ".")
  }
  return $version
}
#EndRegion '.\Private\Get-AppFactoryAppVersionNormalization.ps1' 22
#Region '.\Private\Get-AppFactoryAzurePSADTAppItem.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to download contacts from Azure Storage Account
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER storageContext
  The azure storage context that has permissions to upload to the Azure Storage Account
  .PARAMETER workingFolder
  Path to the working folder for the process
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryAzurePSADTAppItem {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$storageContext,
    #[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$workingFolder,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose" 
  )
  if($null -eq $application.Version -or $application.Version -eq ""){
    $version = "1.0"
  }
  else{
    $version = $application.version
  }
  $AppItem = [PSCustomObject]@{
    "Version" = $version
    "URI" = $null
    "BlobName" = $null
    "Status" = "Good"
  }
  return $AppItem
}
#EndRegion '.\Private\Get-AppFactoryAzurePSADTAppItem.ps1' 35
#Region '.\Private\Get-AppFactoryAzureStorageAppItem.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to download contacts from Azure Storage Account
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER storageContext
  The azure storage context that has permissions to upload to the Azure Storage Account
  .PARAMETER workingFolder
  Path to the working folder for the process
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryAzureStorageAppItem {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$storageContext,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$workingFolder,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose" 
  )
  try {
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Getting Blob Items"  -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "AzureStorage" -Target "Application Factory Service"
    }
    # Get a list of the blobs in the azure storage container
    $blobItems = Get-AppFactoryAzureStorageBlobContent -storageContext $storageContext -app $application -LogLevel $LogLevel
    if ($null -eq $BlobItems) {
      throw "[$($application.IntuneAppName)] Could not find a setup file in container: $($application.StorageAccountContainerName)"
    }
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Reading <c='green'>latest.json</c>"  -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "AzureStorage" -Target "Application Factory Service"
    }
    # Read the contents of the latest.json file
    $LatestFile = $BlobItems | Where-Object { $PSItem.Name -eq "latest.json" }
    if ($null -eq $LatestFile) {
      throw "[$($application.IntuneAppName)] Unable to find a latest.json file in container $($application.StorageAccountContainerName)"
    }
    # Download and then read the latest.json file
    $LatestSetupFileDestination = Join-Path -Path $workingFolder -ChildPath "LatestFiles" -AdditionalChildPath $application.StorageAccountContainerName
    $LatestSetupFilePath = Join-Path -Path $LatestSetupFileDestination -ChildPath "latest.json"
    if (-not(Test-Path -Path $LatestSetupFileDestination)) {
      New-Item -Path $LatestSetupFileDestination -ItemType "Directory" | Out-Null
    } 
    Get-AzStorageBlobContent -Context $storageContext -Container $application.StorageAccountContainerName -Blob "latest.json" -Destination $LatestSetupFileDestination -Force | Out-Null
    if (!(Test-Path -Path $LatestSetupFilePath)) {
      throw "[$($application.IntuneAppName)] Could not locate latest.json file after attempted download from storage account container"
    }
    # Try to download the setup files that are in the latest.json file
    $LatestSetupFileContent = Get-Content -Path $LatestSetupFilePath | ConvertFrom-Json -ErrorAction Stop
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Found setup file details from <c='green'>latest.json</c>"  -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "AzureStorage" -Target "Application Factory Service"
    }
    $BlobItem = $BlobItems | Where-Object { (([System.IO.Path]::GetExtension($PSItem.Name)) -match ".msi|.exe|.zip|.wim|.iso|.7z") -and ($PSItem.Name -like "$($LatestSetupFileContent.Version)/$($LatestSetupFileContent.SetupName)") } | Sort-Object -Property "LastModified" -Descending | Select-Object -First 1
    if ($null -eq $BlobItem) {
      throw "[$($application.IntuneAppName)] Could not find blob file in container with name from latest.json: $($LatestSetupFileContent.SetupName)"
    } 
    # Construct custom object for return value
    $PSObject = [PSCustomObject]@{
      "Version"  = $LatestSetupFileContent.Version
      "URI"      = -join @($appStorageContext.BlobEndPoint, $application.StorageAccountContainerName, "/", $BlobItem.Name)
      "BlobName" = $BlobItem.Name
      "Status"   = "Good"
    }
  }
  catch {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Error Occured: $($_)" -Level "Error" -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    throw $_
  }
  # Handle return value
  return $PSObject    
}
#EndRegion '.\Private\Get-AppFactoryAzureStorageAppItem.ps1' 72
#Region '.\Private\Get-AppFactoryAzureStorageBlobContent.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get a list of files from a azure storage blob
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER storageContext
  The azure storage context that has permissions to upload to the Azure Storage Account
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryAzureStorageBlobContent{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$storageContext,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"     
  )
  # Construct array list for return value containing file names
  $blobList = [System.Collections.Generic.List[PSCustomObject]]@()
  # Get contents of specific azure blob
  try{
    $StorageBlobContents = Get-AzStorageBlob -Container $application.StorageAccountContainerName -Context $storageContext -ErrorAction Stop
  }
  catch{
    throw $_       
  }
  # Add found files from blob to list
  if ($null -ne $StorageBlobContents) {
    foreach ($StorageBlobContent in $StorageBlobContents) {
      if($script:AppFactoryLogging){
        Write-PSFMessage -Message "[$($application.IntuneAppName)] Found file <c='green'>$($StorageBlobContent.Name)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","AzureStorage" -Target "Application Factory Service"
      }
      $blobList.Add($StorageBlobContent) | Out-Null
    }
  }
  return $blobList    
}
#EndRegion '.\Private\Get-AppFactoryAzureStorageBlobContent.ps1' 39
#Region '.\Private\Get-AppFactoryECNOAppItem.ps1' 0
function Get-AppFactoryECNOAppItem{
  [CmdletBinding()]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$documentLibrary,  
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose" 
  )
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Looking for Sharepoint Application in folder <c='green'>$($application.StorageAccountContainerName)</c>"  -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Sharepoint" -Target "Application Factory Service"  
  }
  $listItems = Get-PnPListItem -List $documentLibrary -PageSize 1000 | Where-Object {$_["FileLeafRef"] -eq "$($application.StorageAccountContainerName)"}
  if($null -eq $listItems){
    throw "[$($application.IntuneAppName)]Unable to find any items in sharepoint for app."
  }
  # Sort so we only get the most recent version of the item if it exists more than once
  $listItems = $listItems | Select-Object -ExpandProperty FieldValues
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Found application information " -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Sharepoint" -Target "Application Factory Service"  
  }
  $files = Get-PnPFolder -Url $listItems.FileRef | Get-PnPFolderItem -ItemType File
  return $files | Select-Object @{Label="Version";Expression={$_.Name -replace ".7z"}},@{Label="URI";Expression={$_.ServerRelativeUrl}},@{Label="BlobName";Expression={$null}},@{Label="Status";Expression={"Good"}} | Sort-Object -Property Version -Descending -Top 1
}
#EndRegion '.\Private\Get-AppFactoryECNOAppItem.ps1' 24
#Region '.\Private\Get-AppFactoryEvergreenAppItem.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to interact with the evergreen powershell module to find the installers for evergreen based installers
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryEvergreenAppItem{
  [CmdletBinding()]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"    
  )
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Looking for Evergreen Application with AppID Filter: <c='green'>$($application.AppID)</c>"  -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service" 
  }
  # Construct array list to build the dynamic filter list
  $FilterList = [System.Collections.Generic.List[PSCustomObject]]@()
  # Process known filter properties and add them to array list if present on current object
  if ($Application.FilterOptions.Architecture) {
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Architecture Filter: <c='green'>$($Application.FilterOptions.Architecture)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service"
    }
    $FilterList.Add("`$PSItem.Architecture -eq ""$($Application.FilterOptions.Architecture)""") | Out-Null
  }
  if ($Application.FilterOptions.Platform) {
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Platform Filter: <c='green'>$($Application.FilterOptions.Platform)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service"
    }
    $FilterList.Add("`$PSItem.Platform -eq ""$($Application.FilterOptions.Platform)""") | Out-Null
  }
  if ($Application.FilterOptions.Channel) {
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Channel Filter: <c='green'>$($Application.FilterOptions.Channel)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service"
    }
    $FilterList.Add("`$PSItem.Channel -eq ""$($Application.FilterOptions.Channel)""") | Out-Null
  }
  if ($Application.FilterOptions.Type) {
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Type Filter: <c='green'>$($Application.FilterOptions.Type)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service"
    }
    $FilterList.Add("`$PSItem.Type -eq ""$($Application.FilterOptions.Type)""") | Out-Null
  }
  if ($Application.FilterOptions.InstallerType) {
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] InstallerType Filter: <c='green'>$($Application.FilterOptions.InstallerType)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service"
    }
    $FilterList.Add("`$PSItem.InstallerType -eq ""$($Application.FilterOptions.InstallerType)""") | Out-Null
  }
  if ($Application.FilterOptions.Release) {
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Release Filter: <c='green'>$($Application.FilterOptions.Release)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service"
    }
    $FilterList.Add("`$PSItem.Release -eq ""$($Application.FilterOptions.Release)""") | Out-Null
  }  
  if ($Application.FilterOptions.Language) {
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Language Filter: <c='green'>$($Application.FilterOptions.Language)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service"
    }
    $FilterList.Add("`$PSItem.Language -eq ""$($Application.FilterOptions.Language)""") | Out-Null
  }    
  if ($Application.FilterOptions.ImageType) {
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] ImageType Filter: <c='green'>$($Application.FilterOptions.ImageType)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Evergreen" -Target "Application Factory Service"
    }
    $FilterList.Add("`$PSItem.ImageType -eq ""$($Application.FilterOptions.ImageType)""") | Out-Null
  } 
  # Construct script block from filter list array
  $FilterExpression = [scriptblock]::Create(($FilterList -join " -and ")) 
  # Get the evergreen app based on dynamic filter list
  if($FilterList.Count -gt 0){
    $EvergreenApp = Get-EvergreenApp -Name $application.AppId | Where-Object -FilterScript $FilterExpression | Sort-Object Version -Descending | Select-Object -first 1
  }
  else{
    $EvergreenApp = Get-EvergreenApp -Name $application.AppId | Sort-Object Version -Descending | Select-Object -first 1
  }
  # Only return the top item
  $PSObject = [PSCustomObject]@{
    "Version" = $EvergreenApp.version
    "URI" = $EvergreenApp.URI
    "BlobName" = $null
    "Status" = "Good"
  }
  return $PSObject  
}
#EndRegion '.\Private\Get-AppFactoryEvergreenAppItem.ps1' 88
#Region '.\Private\Get-AppFactoryInstallerECNO.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to download the app installers from a sharepoint document library
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER AppSetupFolderPath
  The path that the app setup folders are stored in
  .PARAMETER supportFiles
  The path to the support files folder
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryInstallerECNO{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$AppSetupFolderPath,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$supportFiles,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading from Sharepoint" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Download" -Target "Application Factory Service"
  }
  try {
    New-Item -Path $AppSetupFolderPath -ItemType Directory -ErrorAction "Stop" | Out-Null
  }
  catch {
    throw "[$($application.IntuneAppName)] Failed to create '$($AppSetupFolderPath)' with error message: $($_.Exception.Message)"
  }  
  try{
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading setupfile <c='green'>$($application.FullPath)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","ECNO" -Target "Application Factory Service"
    }
    Get-PnPFile -Url $application.FullPath -AsFile -Path $AppSetupFolderPath -Filename $application.AppSetupFileName -Force
  }
  catch{
    throw "[$($application.IntuneAppName)] Failed to download file with error message: $($_.Exception.Message)"
  }
  $appName = ($application.IntuneAppName -replace "STS-","")
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Expanding Archive" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Download" -Target "Application Factory Service"
  }
  # Expand 7zip file
  Expand-7ZIpFile -application $application -applicationName $appName -path $AppSetupFolderPath -supportFiles $supportFiles -LogLevel $LogLevel
  Remove-Item -Path (Join-Path -Path $AppSetupFolderPath -ChildPath $application.AppSetupFileName) -Force
  $application.AppSetupFileName = "_action.ps1"
  return $application    
}
#EndRegion '.\Private\Get-AppFactoryInstallerECNO.ps1' 50
#Region '.\Private\Get-AppFactoryInstallerFiles.ps1' 0
function Get-AppFactoryInstallerFiles {
  [CmdletBinding()]
  [OutputType([String])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$appStorageContext,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$workspace,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$supportFiles,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"    
  )
  $AppSetupFolderPath = Join-Path -Path $workspace -ChildPath "Installers" -AdditionalChildPath $application.AppFolderName
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Attempting to download <c='green'>$($application.URI)</c> to <c='green'>$($AppSetupFolderPath)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "Download" -Target "Application Factory Service"
  }
  try {
    switch ($application.AppSource) {
      "StorageAccount" {
        Get-AppFactoryInstallerStorageAccount -application $application -AppSetupFolderPath $AppSetupFolderPath -storageContext $appStorageContext -LogLevel $LogLevel
      }
      "Sharepoint" {
        Get-AppFactoryInstallerSharepoint -application $application -AppSetupFolderPath $AppSetupFolderPath -supportFiles $supportFiles -LogLevel $LogLevel
      }
      "ECNO" {
        $application = Get-AppFactoryInstallerECNO -application $application -AppSetupFolderPath $AppSetupFolderPath -supportFiles $supportFiles -LogLevel $LogLevel
      }
      "PSADT" {
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] No Download Neccessary" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Download" -Target "Application Factory Service"
        }        
      }
      default {
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading from HTTP" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Download" -Target "Application Factory Service"
        }        
        Save-InstallerFile -application $application -path $AppSetupFolderPath -LogLevel $LogLevel
      }  
    }
    # Validate setup installer was successfully downloaded
    $AppSetupFilePath = Join-Path -Path $AppSetupFolderPath -ChildPath $application.AppSetupFileName 
    if (-not (Test-Path -Path $AppSetupFilePath)  -and $application.AppSource -ne "PSADT"){
      throw "[$($application.IntuneAppName)] Could not detect downloaded setup installer"
    }
  } 
  catch {
    throw $_
  }
  return $AppSetupFolderPath
}
#EndRegion '.\Private\Get-AppFactoryInstallerFiles.ps1' 49
#Region '.\Private\Get-AppFactoryInstallerSharepoint.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to download the app installers from a sharepoint document library
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER AppSetupFolderPath
  The path that the app setup folders are stored in
  .PARAMETER supportFiles
  The path to the support files folder
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryInstallerSharepoint{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$AppSetupFolderPath,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$supportFiles,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading from Sharepoint" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Download" -Target "Application Factory Service"  
  }
  try {
    New-Item -Path $AppSetupFolderPath -ItemType Directory -ErrorAction "Stop" | Out-Null
  }
  catch {
    throw "[$($application.IntuneAppName)] Failed to create '$($AppSetupFolderPath)' with error message: $($_.Exception.Message)"
  }
  # Connect to Sharepoint
  try{
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading setupfile <c='green'>$($application.FullPath)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Sharepoint" -Target "Application Factory Service"  
    }
    Get-PnPFile -Url $application.FullPath -AsFile -Path $AppSetupFolderPath -Filename $application.AppSetupFileName -Force
    if($application.archiveType -like "*ZIP"){
      $currentFilename = Join-Path -Path $AppSetupFolderPath -ChildPath $application.AppSetupFileName
      $zipFile = Join-Path -Path $AppSetupFolderPath -ChildPath ($application.AppSetupFileName -replace "msi|exe|ps1","zip")
      Rename-Item -Path $currentFilename -NewName ($application.AppSetupFileName -replace "msi|exe|ps1","zip")
      Expand-Archive -Path $zipFile -DestinationPath $AppSetupFolderPath -Force
    }
    elseif($application.archiveType -like "7Z"){
      throw "Not yet implemented"
    }
    try{
      Remove-Item -Path $zipFile -Force
    }
    catch{
      throw "[$($application.IntuneAppName)] Failed to cleanup: $($_.Exception.Message)"
    }        
  }
  catch{
    throw "[$($application.IntuneAppName)] Failed to download file with error message: $($_.Exception.Message)"
  }
}
#EndRegion '.\Private\Get-AppFactoryInstallerSharepoint.ps1' 56
#Region '.\Private\Get-AppFactoryInstallerStorageAccount.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to download the app installers from azure storage
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER AppSetupFolderPath
  The path that the app setup folders are stored in
  .PARAMETER storageContext
  The azure storage context that has permissions to upload to the Azure Storage Account
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryInstallerStorageAccount {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$AppSetupFolderPath,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$storageContext,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Get Azure Storage Blob Contents
  Get-StorageAccountBlobContent -storageContext $storageContext -application $application -path $AppSetupFolderPath
  # Process files if they are an archived type
  if($application.archiveType -like "*ZIP"){
    $zipFile = Join-Path -Path $AppSetupFolderPath -ChildPath ($application.AppSetupFileName -replace "msi|exe|ps1","zip")
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Expanding Archive" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Download" -Target "Application Factory Service"
    }
    Expand-Archive -Path $zipFile -DestinationPath $AppSetupFolderPath -Force
    try{
      Remove-Item -Path $zipFile -Force
    }
    catch{
      throw "[$($application.IntuneAppName)] Failed to cleanup: $($_.Exception.Message)"
    }    
  }
  elseif($application.archiveType -like "7Z"){
    throw "Not yet implemented"
  }
}
#EndRegion '.\Private\Get-AppFactoryInstallerStorageAccount.ps1' 41
#Region '.\Private\Get-AppFactorySharepointAppItem.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get the details of an application installer stored in sharepoint
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER documentLibrary
  The name of the document library that is storing the application files
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactorySharepointAppItem{
  [CmdletBinding()]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$documentLibrary,  
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose" 
  )
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Looking for Sharepoint Application Installer for <c='green'>$($application.IntuneAppName)</c>"  -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Sharepoint" -Target "Application Factory Service"  
  }
  # Get List of Files
  $name = $application.IntuneAppName -replace "STS-",""  
  $listItems = Get-PnPListItem -List $documentLibrary -PageSize 1000 | Where-Object {$_["FileLeafRef"] -like "$($name)*"}
  if($null -eq $listItems){
    throw "[$($application.IntuneAppName)]Unable to find any items in sharepoint for app."
  }
  # Sort so we only get the most recent version of the item if it exists more than once
  $listItems = $listItems | Select-Object -ExpandProperty FieldValues
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Found application information " -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Sharepoint" -Target "Application Factory Service"  
  }
  return $listItems | Select-Object @{Label="Version";Expression={$_.SoftwareVersion}},@{Label="URI";Expression={$_.FileRef}},@{Label="BlobName";Expression={$null}},@{Label="Status";Expression={"Good"}} | Sort-Object -Property Version -Descending -Top 1    
}
#EndRegion '.\Private\Get-AppFactorySharepointAppItem.ps1' 35
#Region '.\Private\Get-AppFactoryWinGetAppItem.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get the details of an application installer that is acquired from winget
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER documentLibrary
  The name of the document library that is storing the application files
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryWinGetAppItem{
  [CmdletBinding()]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"    
  )
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Looking for Winget Application with AppID Filter: <c='green'>$($application.AppID)</c>"  -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","WinGet" -Target "Application Factory Service"
  }
  # Create the argument and then run the winget cmd line
  $WinGetArguments = @("search", "$($application.AppID)")
  $WinGetStream = & "winget" $WinGetArguments | Out-String -Stream 
  # Confirm that a package was found
  foreach ($RowItem in $WinGetStream) {
    if ($RowItem -eq "No package found matching input criteria.") {
      throw "[$($application.IntuneAppName)] No package found matching specified id: $($application.AppId)"
    }
  }
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Found winget item" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","WinGet" -Target "Application Factory Service"
  }
  # Now look for the specific item and get the details and parse them out
  $WinGetArguments = @("show", "$($application.AppID)")
  $WinGetStream = & "winget" $WinGetArguments | Out-String -Stream
  $PSObject = [PSCustomObject]@{
    "Version" = ($WinGetStream | Where-Object { $PSItem -match "^Version\:.*(?<AppVersion>(\d+(\.\d+){0,3}))$" }).Replace("Version:", "").Trim()
    "URI" = (($WinGetStream | Where-Object { $PSItem -match "^.*(Download|Installer) Url\:.*$" }) -replace "(Download|Installer) Url:", "").Trim()
    "BlobName" = $null
    "Status" = "Good"
  }
  return $PSObject  
}
#EndRegion '.\Private\Get-AppFactoryWinGetAppItem.ps1' 44
#Region '.\Private\Get-StorageAccountBlobContent.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get the contents of a azure storage blob
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER storageContext
  The azure storage context that has permissions to upload to the Azure Storage Account
  .PARAMETER path
  The path for the contents
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-StorageAccountBlobContent {
  param (
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$storageContext,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,    
    [parameter(Mandatory = $true, HelpMessage = "Specify the download path.")][ValidateNotNullOrEmpty()][string]$path,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"     
  )
  # Cleanup possible remnants
  Remove-Item -Path $Path -Force -Recurse -ErrorAction SilentlyContinue
  try {
    New-Item -Path $Path -ItemType Directory -ErrorAction "Stop" | Out-Null
  }
  catch {
    throw "[$($application.IntuneAppName)] Failed to create '$($Path)' with error message: $($_.Exception.Message)"
  }
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading setupfile <c='green'>$($application.BlobName)</c>" -Tag "Application","$($application.IntuneAppName)","AzureStorage" -Target "Application Factory Service"
  }
  # Get the storage blob contents
  try{
    Get-AzStorageBlobContent -Context $storageContext -Container $application.StorageAccountContainerName -Blob $application.BlobName -Destination $Path -Force -ErrorAction "Stop" | Out-Null
  }
  catch{
    throw "[$($application.IntuneAppName)] Unable to save app file: $($_.Exception.Message)"
  }
  # Move it to the appropriate location
  try{
    Move-Item -Path "$($Path)\$($application.BlobName)" -Destination $Path -ErrorAction Stop
    Remove-Item -Path "$($Path)\$($application.AppSetupVersion)" -Recurse -ErrorAction Stop
  }
  catch{
    throw "[$($application.IntuneAppName)] Failed to move item and cleanup: $($_.Exception.Message)"
  }
}
#EndRegion '.\Private\Get-StorageAccountBlobContent.ps1' 47
#Region '.\Private\New-AppFactoryAppFolder.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to create a new application to be handled by the automated process
  .PARAMETER Name
  The name of the application that we are creating
  .PARAMETER force
  Continue even if the folder already exists, overwriting the current details
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
 
  .EXAMPLE
 
  Create a new application folder
  New-AppFactoryAppFolder -Name "### Name ###" -LogLevel "Output"
#>

function New-AppFactoryAppFolder {
  [CmdletBinding()]
  [OutputType([string])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$Name,
    [Parameter()][bool]$force,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Template folder
  $templateFolderPath = Join-Path -Path $PSScriptRoot -ChildPath "Templates"
  # Path to the new app folder
  $appFolderPath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $Name
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($Name)] Template folder used <c='green'>$($templateFolderPath)</c>" -Level $LogLevel -Tag "Application", "$($Name)" -Target "Application Factory Service"
    Write-PSFMessage -Message "[$($Name)] Creating application folder <c='green'>$($appFolderPath)</c>" -Level $LogLevel -Tag "Application", "$($Name)" -Target "Application Factory Service"
  }
  # Create New Application Folder
  try {
    if ((Test-Path $appFolderPath) -and -not $force) {
      throw "Folder Already Exists $($templateFolderPath) and force was not set"
    }
    New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
  }
  catch {
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($Name)" -Target "Application Factory Service"
    throw $_
  }
  # Copy Template Folders for the application
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($Name)] Copying Template Files to <c='green'>$($appFolderPath)</c>" -Level $LogLevel -Tag "Application", "$($Name)" -Target "Application Factory Service"
  }
  $filesToCopy = @{
    "AppJsonFile"      = Join-Path -Path $templateFolderPath -ChildPath "Application" -AdditionalChildPath "App.json"
    "DeployAppFile"    = Join-Path -Path $templateFolderPath -ChildPath "Application" -AdditionalChildPath "Deploy-Application.ps1"
    "IconFile"         = Join-Path -Path $templateFolderPath -ChildPath "Framework" -AdditionalChildPath "Icon.png"    
  }
  try {
    foreach ($file in $filesToCopy.GetEnumerator()) {
      Copy-Item -Path $file.value -Destination $appFolderPath -ErrorAction Stop -Force
    }
  }
  catch {
    Remove-Item -Path $appFolderPath -Force
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($Name)" -Target "Application Factory Service"
    throw $_
  }
  return $appFolderPath
}
#EndRegion '.\Private\New-AppFactoryAppFolder.ps1' 64
#Region '.\Private\New-AppFactoryIntuneDeployment.ps1' 0
function New-AppFactoryIntuneDeployment{
  param(
  )
  Get-Variable -Scope Script  
}
#EndRegion '.\Private\New-AppFactoryIntuneDeployment.ps1' 6
#Region '.\Private\New-AppFactoryIntuneWin.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to create the intune win file to upload to intune
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function New-AppFactoryIntuneWin{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Creating Intune App File"  -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service" 
  }
  # Get Application config file and read it
  $AppDataFile = Join-Path -Path $application.AppPublishFolderPath -ChildPath "App.json"
  $AppData = Get-Content -Path $AppDataFile | ConvertFrom-Json
  # Get the source and output folders and then create intune win file
  $SourceFolder = Join-Path -Path $application.AppPublishFolderPath -ChildPath $AppData.PackageInformation.SourceFolder
  $OutputFolder = Join-Path -Path $application.AppPublishFolderPath -ChildPath $AppData.PackageInformation.OutputFolder
  $IntuneAppPackage = New-IntuneWin32AppPackage -SourceFolder $SourceFolder -SetupFile $AppData.PackageInformation.SetupFile -OutputFolder $OutputFolder -Force
  if(-not $IntuneAppPackage){
    throw "Error creating application file"
  }  
  # Confirm the file was created and then rename and cleanup
  $AppFileName = Join-Path -Path $OutputFolder -ChildPath "$($application.IntuneAppName).intunewin"
  Remove-Item -Path $AppFileName -Force -ErrorAction SilentlyContinue
  Rename-Item -Path $IntuneAppPackage.Path -NewName "$($application.IntuneAppName).intunewin" -Force
  $application | Add-Member -MemberType NoteProperty -Name 'IntunePackage' -Value $AppFileName -Force
  return $application
}
#EndRegion '.\Private\New-AppFactoryIntuneWin.ps1' 36
#Region '.\Private\New-AppFactoryPackageFolder.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to create the files for an application package
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER AppsPublishRootPath
  Root path for where the publish folders will be used from
  .PARAMETER AppFactoryWorkspace
  Where is the workspace to perform process actions
  .PARAMETER AppFactorySourceDir
  The source directory of the process
  .PARAMETER FrameworkPath
  Where the framework files are stored
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function New-AppFactoryPackageFolder {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$AppsPublishRootPath,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$AppFactoryWorkspace,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$AppFactorySourceDir,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$AppFactoryTemplateDir,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Get the path to where the folder should be, clean up any previous and create it
  $AppPublishFolderPath = Join-Path -Path $AppsPublishRootPath -ChildPath $application.AppFolderName
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Creating app package folder <C='green'>$($AppPublishFolderPath)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  Remove-Item -Path $AppPublishFolderPath -Force -Recurse -ErrorAction SilentlyContinue
  try {
    New-Item -Path $AppPublishFolderPath -ItemType Directory -ErrorAction "Stop" | Out-Null
  }
  catch {
    throw "[$($application.IntuneAppName)] Failed to create '$($AppPublishFolderPath)' with error message: $($_.Exception.Message)"
  }
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Copying framework files" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  # Copy the framework files to the new folder
  try {
    Copy-Item -Path "$($AppFactoryTemplateDir)\Framework\*" -Destination $AppPublishFolderPath -Recurse -Force -Confirm:$false
  }
  catch {
    throw "[$($application.IntuneAppName)] Unable to copy template files: $($_.Exception.Message)"
  } 
  # Where the files for the installer should be stored, create the folder
  $AppsPublishSourceFilesPath = Join-Path -Path $AppsPublishRootPath -ChildPath $application.AppFolderName -AdditionalChildPath "source", "files"
  New-Item -Path $AppsPublishSourceFilesPath -ItemType "Directory" -Force -Confirm:$false | Out-Null
  if($application.AppSource -ne "PSADT"){
    $AppInstallerDestinationPath = Join-Path -Path $AppPublishFolderPath -ChildPath "Source" -AdditionalChildPath "Files"
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Copying application files" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target  "Application Factory Service"
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Source path: <c='green'>$($application.AppSetupFolderPath)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target  "Application Factory Service"
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Destination path: <c='green'>$($AppInstallerDestinationPath)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target  "Application Factory Service"
    }
    Copy-Item -Path "$($application.AppSetupFolderPath)\*" -Destination $AppInstallerDestinationPath -Force -Confirm:$false -Recurse
  }
  # Update deploy-application.ps1 in the publish folder
  Update-DeployApplication -application $application -AppFactoryWorkspace $AppFactoryWorkspace -AppFactorySourceDir $AppFactorySourceDir -LogLevel $LogLevel
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Copying Icon File" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  $iconPath = Join-Path -Path $AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath  $application.AppFolderName,"Icon.png"
  $iconDestination = Join-Path -Path $AppPublishFolderPath -ChildPath "Icon.png"
  Copy-Item -Path $iconPath -Destination $iconDestination -Force -Confirm:$false
  # Update app.json in the publish folder
  Update-AppJSON -application $application -AppFactoryWorkspace $AppFactoryWorkspace -AppFactorySourceDir $AppFactorySourceDir -LogLevel $LogLevel
  $AppRootFolderPackagePath = Join-Path -Path $AppPublishFolderPath -ChildPath "Package"
  if (-not(Test-Path -Path $AppRootFolderPackagePath)) {
    New-Item -Path $AppRootFolderPackagePath -ItemType "Directory" -Force -Confirm:$false | Out-Null
  }
  $application | Add-Member -MemberType NoteProperty -Name 'AppPublishFolderPath' -Value $AppPublishFolderPath -Force
  return $application  
}
#EndRegion '.\Private\New-AppFactoryPackageFolder.ps1' 78
#Region '.\Private\Save-InstallerFile.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to downloaded files for the package
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER application
  The path to store the data
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Save-InstallerFile {
  param (
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$path,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose" 
  )
  # Ensure we are using TLS 1.2
  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
  # Create path if it doesn't exist
  if (-not(Test-Path -Path $path -PathType "Container")) {
    try {
      New-Item -Path $path -ItemType "Container" -ErrorAction "Stop" | Out-Null
    }
    catch [System.Exception] {
      throw "[$($application.IntuneAppName)] Failed to create '$($Path)' with error message: $($_.Exception.Message)"
    }
  }
  # Download installer file
  try {
    $OutFilePath = Join-Path -Path $Path -ChildPath $application.AppSetupFileName
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading setupfile <c='green'>$($application.URI)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)","Download" -Target "Application Factory Service"
    }
    Invoke-WebRequest -Uri $application.URI -OutFile $OutFilePath -UseBasicParsing -ErrorAction "Stop"
  }
  catch [System.Exception] {
    throw "[$($application.IntuneAppName)] Failed to download file from '$($application.URI)' with error message: $($_.Exception.Message)"
  }  
}
#EndRegion '.\Private\Save-InstallerFile.ps1' 40
#Region '.\Private\Set-AppFactoryDeploymentScriptLines.ps1' 0
function Set-AppFactoryDeploymentScriptLines {
  [cmdletbinding()]
  [OutputType([String])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[String[]]]$ScriptLines,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$Script,
    [Parameter(Mandatory = $true)][ValidateSet("Install", "Uninstall")][String]$section,
    [Parameter()][switch]$main,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  try {
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Reading application deployment file located at <c='green'>$($ApplicationDeploymentFilePath)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    }
    $ApplicationDeploymentFile = Get-Content -Path $Script -Raw
  }
  catch {
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    throw $_   
  }
  if($main.IsPresent){
    if($section -eq "install"){
      $startingLine = ([Regex]::Match($ApplicationDeploymentFile,"###INSTALLTYPE:(NONE|None|MSI|EXE|POWERSHELL|ECNO|CUSTOM)###").Value)
    }
    else{
      $startingLine = ([Regex]::Match($ApplicationDeploymentFile,"###UNINSTALLTYPE:(NONE|None|MSI|EXE|NAME|GUID|ECNO|CUSTOM)###").Value)
    }
  }
  else{
    $startingLine = $ScriptLines[0][0]  
  }
  $endingLine = $ScriptLines[-1][0]
  $lines = [System.Collections.Generic.List[String[]]]@()
  foreach($line in $ScriptLines){
    $addTabs = ""
    for($i = 0; $i -lt $line[1]; $i++){
      $addTabs = "$($addTabs)`t"
    }
    $lines.Add("$($addTabs)$($line[0])")
  }
  $top = $ApplicationDeploymentFile.Split($startingLine)
  $bottom = $top[1].Split($endingLine)
  $bottomLines = $bottom[1].split("`n")
  $bottomLength = $bottomLines.Count 
  $outputFile =  [System.Collections.Generic.List[String[]]]@() 
  $outputFile.Add($top[0].TrimEnd()) | Out-Null
  $outputFile.Add("`t`t") | Out-Null
  foreach($line in $lines){
    $outputFile.Add($line) | Out-Null
  }
  $outputFile.Add($($bottomLines[1..$bottomLength] -join "`n").TrimEnd())
  try{
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Writing application deployment file <c='green'>$($ApplicationDeploymentFilePath)</c>" -Level $LogLevel -Tag "Application","Install","$($application.IntuneAppName)" -Target "Application Factory Service"
    }
    $outputFile | Set-Content -Path $ApplicationDeploymentFilePath -Force  
  }
  catch{
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($Name)" -Target "Application Factory Service"
    throw $_     
  }    
}
#EndRegion '.\Private\Set-AppFactoryDeploymentScriptLines.ps1' 63
#Region '.\Private\Update-AppJSON.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to update the App.JSON file for the application
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER AppFactoryWorkspace
  Workspace directory to work with
  .PARAMETER AppFactorySourceDir
  Source directory for configuration items
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Update-AppJSON{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$AppFactoryWorkspace,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$AppFactorySourceDir,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"  
  )
  $AppsPublishRootPath = Join-Path -Path $AppFactoryWorkspace -ChildPath "Publish"
  $AppPackageFolderPath = Join-Path -Path $AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName
  $AppPublishFolderPath = Join-Path -Path $AppsPublishRootPath -ChildPath $application.AppFolderName 
  $AppInstallerPath = Join-Path -Path $AppPublishFolderPath -ChildPath "Source" -AdditionalChildPath "Files",$application.AppSetupFileName
  $AppFilePath = Join-Path -Path $AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName,"App.json"
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Reading <c='green'>$($AppFilePath)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  try{
    # Read file and update hardcoded variables with specific variable value from app details
    $AppFileContent = Get-Content -Path $AppFilePath | ConvertFrom-Json  
  }
  catch{
    throw "[$($application.IntuneAppName)] Unable to read file: $($_.Exception.Message)"
  }  
  # Update version specific property values
  $AppFileContent.Information.DisplayName = $application.IntuneAppName
  $AppFileContent.Information.AppVersion = $application.AppSetupVersion.toString()
  $AppFileContent.Information.Publisher = $application.AppPublisher 
  $AppFileContent.Information.InformationURL = $application.InformationURL
  $AppFileContent.Information.PrivacyURL = $application.PrivacyURL
  foreach ($DetectionRuleItem in $AppFileContent.DetectionRule) {
    switch ($DetectionRuleItem.Type) {
      "MSI" {
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Retrieving MSI metadata" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
        }
        $ProductCode = Get-MSIMetaData -Path $AppInstallerPath -Property "ProductCode"
        $ProductCode = ($ProductCode -as [string]).Trim()  
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Product Code: $($ProductCode)" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
        }
        $ProductVersion = Get-MSIMetaData -Path $AppInstallerPath -Property "ProductVersion"
        $ProductVersion = ($ProductVersion -as [string]).Trim()
        # Normalize App Version Numbers
        $ProductVersion = (Get-AppFactoryAppVersionNormalization -version $ProductVersion).ToString()
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Product Version: $($ProductVersion)" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
        }
        # Update App.json detection rule with MSI meta data for ProductCode
        if ($null -ne $ProductCode) {
          $DetectionRuleItem.ProductCode = $ProductCode
        }
        else {
          throw "[$($application.IntuneAppName)] Failed to retrieve MSI meta data value for ProductCode: $($_.Exception.Message)"
        }
        # Update App.json detection rule with MSI meta data for ProductVersion
        if ($null -ne $ProductVersion) {
          $DetectionRuleItem.ProductVersion = $ProductVersion
        }
        else {
          throw "[$($application.IntuneAppName)] Failed to retrieve MSI meta data value for ProductVersion: $($_.Exception.Message)"
        } 
      }
      "Registry" {
        if ($DetectionRuleItem.KeyPath -match "(\#{3})PRODUCTCODE(\#{3})") {
          if($script:AppFactoryLogging){
            Write-PSFMessage -Message "[$($application.IntuneAppName)] Retrieving MSI metadata" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
          }
          $ProductCode = Get-MSIMetaData -Path $AppInstallerPath -Property "ProductCode"
          $ProductCode = ($ProductCode -as [string]).Trim()  
          if($script:AppFactoryLogging){
            Write-PSFMessage -Message "[$($application.IntuneAppName)] Product Code: $($ProductCode)" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
          }
          if ($null -ne $ProductCode) {
            $DetectionRuleItem.KeyPath = $DetectionRuleItem.KeyPath -replace "###PRODUCTCODE###", ($ProductCode -as [string]).Trim()
          }
          else {
            throw "[APPLICATION: <c='green'>$($application.IntuneAppName)</c>] Failed to retrieve MSI meta data value for ProductVersion: $($_.Exception.Message)"
          }
        }
        switch ($DetectionRuleItem.DetectionMethod) {
          "VersionComparison" {
            $DetectionRuleItem.Value = $application.AppSetupVersion.toString()
          }
          "StringComparison" {
            $DetectionRuleItem.Value = $application.AppSetupVersion.toString()
          }
        }
      }
      "File" {
        switch ($DetectionRuleItem.DetectionMethod) {
          "Version" {
            $DetectionRuleItem.VersionValue = $application.AppSetupVersion.toString()
          }
        }        
      }
      "Script" {
        # Create the Scripts folder in the app package folder in the publish root folder
        $AppPublishScriptsFolderPath = Join-Path -Path $AppPublishFolderPath -ChildPath "Scripts"
        if (-not(Test-Path -Path $AppPublishScriptsFolderPath)) {
          New-Item -Path $AppPublishScriptsFolderPath -ItemType "Directory" -Force -Confirm:$false | Out-Null
        }
        # Construct the detection script file path from the app package folder and destination path for copy operation
        $AppDetectionScriptFile = Join-Path -Path $AppPackageFolderPath -ChildPath $AppFileContent.DetectionRule.ScriptFile
        $AppDetectionScriptFileDestinationPath = Join-Path -Path $AppPublishScriptsFolderPath -ChildPath $AppFileContent.DetectionRule.ScriptFile 
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Copying detection script" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
        }
        try{
          Copy-Item -Path $AppDetectionScriptFile -Destination $AppDetectionScriptFileDestinationPath -Force -Confirm:$false
        }
        catch{
          throw "[APPLICATION: <c='green'>$($application.IntuneAppName)</c>] Unable to copy detection script: $($_.Exception.Message)"
        }
        $AppDetectionScriptFileContent = Get-Content -Path $AppDetectionScriptFileDestinationPath
        $AppDetectionScriptFileContent = $AppDetectionScriptFileContent -replace "###VERSION###", $application.AppSetupVersion.toString()
        # Update detection script file in app package folder in publish folder root
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Writing out detection script"-Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
        }
        try{
          Out-File -InputObject $AppDetectionScriptFileContent -FilePath $AppDetectionScriptFileDestinationPath -Encoding "utf8" -Force -Confirm:$false
        }
        catch{
          throw "[APPLICATION: <c='green'>$($application.IntuneAppName)</c>] Unable to write out detection script: $($_.Exception.Message)"
        }
      }
    }
  }
  # Save changes made to App.json in app package folder in publish root folder
  $AppFileDestinationPath = Join-Path -Path $AppPublishFolderPath -ChildPath "App.json"
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Writing out App.JSON file"-Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  try{
    Out-File -InputObject ($AppFileContent | ConvertTo-Json -depth 5) -FilePath $AppFileDestinationPath -Encoding "utf8" -Force -Confirm:$false  
  }
  catch{
    throw "[$($application.IntuneAppName)] Unable to write out file: $($_.Exception.Message)"
  }
}
#EndRegion '.\Private\Update-AppJSON.ps1' 153
#Region '.\Private\Update-DeployApplication.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to update the Deploy-Application.ps1 file for the application
  .PARAMETER application
  The application object that we are working for so that we can ensure that get the correct and current data
  .PARAMETER AppFactoryWorkspace
  Workspace directory to work with
  .PARAMETER AppFactorySourceDir
  Source directory for configuration items
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Update-DeployApplication{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$AppFactoryWorkspace,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$AppFactorySourceDir,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"  
  )
  $AppsPublishRootPath = Join-Path -Path $AppFactoryWorkspace -ChildPath "Publish"
  $AppFilePath = Join-Path -Path $AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName,"Deploy-Application.ps1"  
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Reading <c='green'>$($AppFilePath)</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  try{
    # Read file and update hardcoded variables with specific variable value from app details
    $AppFileContent = Get-Content -Path $AppFilePath
  }
  catch{
    throw "[$($application.IntuneAppName)] Unable to read file: $($_.Exception.Message)"
  } 
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Substituting placeholders" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  $AppFileContent = $AppFileContent -replace "###INTUNEAPPNAME###", $application.IntuneAppName
  $AppFileContent = $AppFileContent -replace "###APPPUBLISHER###", $application.AppPublisher
  $AppFileContent = $AppFileContent -replace "###VERSION###", $application.AppSetupVersion
  $AppFileContent = $AppFileContent -replace "###DATETIME###", (Get-Date).ToShortDateString()
  $AppFileContent = $AppFileContent -replace "###SETUPFILENAME###", $($application.AppSetupFileName)  
  $AppsPublishSourcePath = Join-Path -Path $AppsPublishRootPath -ChildPath "$($application.AppFolderName)\Source" -AdditionalChildPath "Deploy-Application.ps1"
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Writing out file" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  Out-File -InputObject $AppFileContent -FilePath $AppsPublishSourcePath -Encoding "utf8" -Force -Confirm:$false   
}
#EndRegion '.\Private\Update-DeployApplication.ps1' 47
#Region '.\Public\Add-AppFactoryPackageFolder.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to process through the application list and prepare the package folder
  .PARAMETER applicationList
  The collection of the applications that we will be acting on
  .PARAMETER multithread
  Show we proceed single threaded or multithreaded
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Add-AppFactoryPackageFolder {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter()][ValidateNotNullOrEmpty()][switch]$multithread,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"  
  )
  # Create a list to store the results of the process
  $applications = [System.Collections.Generic.List[PSCustomObject]]@()  
  # The require application paths used during the scripts
  $AppsPublishRootPath = Join-Path -Path $script:AppFactoryWorkspace -ChildPath "Publish"  
  if (-not $multithread.IsPresent) {
    foreach ($application in $applicationList) {
      try {
        $app = New-AppFactoryPackageFolder -application $application -AppsPublishRootPath $AppsPublishRootPath -AppFactoryWorkspace $script:AppFactoryWorkspace -AppFactorySourceDir $script:AppFactorySourceDir -AppFactoryTemplateDir $script:AppFactorySupportTemplateFolder -LogLevel $LogLevel
        $applications.Add($app)
      }
      catch {
        throw $_
      }      
    }
  }
  else {
    # Create a syncronized hashtable to be used between the threads
    $applist = [hashtable]::Synchronized(@{})
    # Process through the applicationList that are currently in the process
    if ($script:LocalModulePath) {
      $modulePath = $script:LocalModulePath
    }
    else {
      $modulePath = (Get-module ApplicationFactoryService).path
    }
    $applicationList | Foreach-Object -Parallel {
      Import-Module -Name $using:modulePath -Force
      $application = $_
      $dict = $using:applist
      $script:AppFactoryLogging = $script:global:AppFactoryLogging
      $AppsPublishRootPath = $using:AppsPublishRootPath
      $script:AppFactoryWorkspace = $using:script:AppFactoryWorkspace
      $script:AppFactorySourceDir = $using:script:AppFactorySourceDir
      $script:AppFactorySupportTemplateFolder = $using:script:AppFactorySupportTemplateFolder
      $LogLevel = $using:LogLevel
      try {
        $app = New-AppFactoryPackageFolder -application $application -AppsPublishRootPath $AppsPublishRootPath -AppFactoryWorkspace $script:AppFactoryWorkspace -AppFactorySourceDir $script:AppFactorySourceDir -AppFactoryTemplateDir $script:AppFactorySupportTemplateFolder -LogLevel $LogLevel
        # If no error, add it to the collection as good status
        $obj = [PSCustomObject]@{
          "Status"      = "Good"
          "Application" = $app 
          "Error"       = $null
        }
        $dict.add($application.GUID, $obj)
      }
      catch {
        # If error, add it to the collection as fail status
        $obj = [PSCustomObject]@{
          "Status"               = "Fail"
          "AppPublishFolderPath" = $null
          "Error"                = $_
        }
        $dict.add($application.GUID, $obj)
      }
    }
    # Only add the items that had good status to the collection for the process
    foreach ($item in $applist.GetEnumerator()) {
      if ($item.Value.status -eq "Good") {
        $applications.Add($item.Value.Application) | Out-Null
      }
    }       
  }
  return $applications
}
#EndRegion '.\Public\Add-AppFactoryPackageFolder.ps1' 83
#Region '.\Public\Compare-AppFactoryAppVersions.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to look at applications in the process, and what their newest version are and compare the results
  .PARAMETER applicationList
  The collection of the applications that we will be acting on
  .PARAMETER appVersions
  An collection of the expected app versions based on their newest version
  .PARAMETER force
  If we should proceed with the repackage even if the current version is already packaged
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Compare-AppFactoryAppVersions {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Hashtable]$appVersions,
    [Parameter()][ValidateNotNullOrEmpty()][switch]$force,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"  
  )
  # Create blank list to store the applications that we will be moving forward with.
  $applications = [System.Collections.Generic.List[PSCustomObject]]@()
  # Loop through application list, removing those that are not required to be updated
  foreach ($application in $applicationList) {
    $version = $appVersions.$($application.GUID)
    if ($script:AppFactoryLogging) {
      if ($null -eq $application.Version) { $reportVersion = "Not Packaged" }
      else { $reportVersion = $application.Version }
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Checking current version <c='red'>$($reportVersion)</c> against new version <c='green'>$($version.version)</c>"  -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service" 
    }
    $version.version = Get-AppFactoryAppVersionNormalization -version $version.version
    # Set the default to false, and implicitly have to add to true to move on
    $packageApplication = $false
    if ($null -eq $application.Version -or $application.Version -eq "" -or [System.Version]$version.version -gt [System.Version]$application.Version) {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($application.IntuneAppName)] <c='green'>Newer version exists for application</c>"   -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "WinGet" -Target "Application Factory Service" 
      }
      $packageApplication = $true
    }
    elseif ($force -eq $true) {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($application.IntuneAppName)] <c='yellow'>Newer version does not exist for application, but flagged for override</c>"   -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "WinGet" -Target "AppFactory"
      }
      $packageApplication = $true
    }
    # If we are meant to package, add a number of items to the object so that we have the required details
    if ($packageApplication) {
      $application | Add-Member -MemberType NoteProperty -Name 'AppSetupVersion' -Value $version.Version
      $application | Add-Member -MemberType NoteProperty -Name 'URI' -Value $version.URI
      $application | Add-Member -MemberType NoteProperty -Name 'FullPath' -Value $version.URI
      $application | Add-Member -MemberType NoteProperty -Name 'BlobName' -Value $(if ($null -ne $version.BlobName) { $version.BlobName } else { [string]::Empty })      
      $applications.Add($application) | Out-Null
    }
    else {
      if ($global:AppFactoryLogging) {
        Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Latest version of application is already published</c>"   -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "WinGet" -Target "AppFactory"      
        Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Removing from application list</c>"   -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "WinGet" -Target "AppFactory"
      }
    }     
  }
  # Return the list of applications
  return $applications  
}
#EndRegion '.\Public\Compare-AppFactoryAppVersions.ps1' 65
#Region '.\Public\Get-AppFactoryApp.ps1' 0
<#
  .DESCRIPTION
  This cmdlet returns a list of applications that are part of the packaging process
  .PARAMETER appSource
  This is a filtering parameter for specific appsources
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER IntuneAppName
  The unique name of the application that we want to work with
  .PARAMETER AppID
  This is a filtering parameter for specific AppID
  .PARAMETER AppPublisher
  This is a filtering parameter for specific application publisher
  .PARAMETER AppFolderName
  This is a filtering parameter for specific application folder
  .PARAMETER StorageAccountContainerName
  This is a filtering parameter for specific application azure storage container
  .PARAMETER archiveType
  This is a filtering parameter for specific application archive types
  .PARAMETER publishTo
  This is a filtering parameter for specific organization
  .PARAMETER public
  This is a filtering parameter for all public applications
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
 
  .EXAMPLE
 
  Get a list of all applications
  Get-AppFactoryApp
 
  Get a list of specific application sources
  Get-AppFactoryApp -appSource "### Application Source ###"
 
  Get a list of applications based on GUID
  Get-AppFactoryApp -GUID "### GUID ###"
 
  Get a list of applications based on AppID
  Get-AppFactoryApp -AppID "### AppID ###"
 
  Get a list of applications based on Application Publisher
  Get-AppFactoryApp -AppPublisher "### Publisher ###"
 
  Get a list of applications based on Application Folder Name
  Get-AppFactoryApp -AppFolderName "### Folder Name ###"
 
  Get a list of applications based on Storage Account Container Name
  Get-AppFactoryApp -StorageAccountContainerName "### Container Name ###"
 
  Get a list of applications based on Archive Type
  Get-AppFactoryApp -archiveType "### Archive Type ###"
 
  Get a list of applications based on Publish To
  Get-AppFactoryApp -publishTo "### Organization ###"
 
  Get a list of all public applications
  Get-AppFactoryApp -public
 
  Get a list of all application with logging enabled
  Get-AppFactoryApp -LogLevel "Output"
#>

function Get-AppFactoryApp{
  [CmdletBinding(DefaultParameterSetName = 'All')]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter()][ValidateSet("Evergreen","Winget","StorageAccount","Sharepoint","PSADT","ECNO")][string]$appSource,
    [Parameter(ParameterSetName = 'GUID')][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter(ParameterSetName = 'IntuneAppName')][ValidateNotNullOrEmpty()][string]$IntuneAppName,
    [Parameter(ParameterSetName = 'AppID')][ValidateNotNullOrEmpty()][string]$AppID,
    [Parameter()][ValidateNotNullOrEmpty()][string]$AppPublisher,
    [Parameter()][ValidateNotNullOrEmpty()][string]$AppFolderName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$StorageAccountContainerName,
    [Parameter()][ValidateSet("WIM","7Z","ZIP","MSIZIP","EXEZIP","PSZIP",$null)][string]$archiveType,
    [Parameter()][ValidateNotNullOrEmpty()][string]$publishTo,
    [Parameter()][switch]$public,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"  
  )
  # Get the path to where the application configs are stored
  $applicationFolders = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps"
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "Getting Applications at path <c='green'>$($applicationFolders)</c>" -Level $LogLevel -Tag "Application" -Target "Application Factory Service"
  }
  # Get a list of all the configuration files for the applications
  $ApplicationConfigFiles = Get-Childitem -Path $applicationFolders -Recurse -Filter "config.json"
  # Create an empty array to store the application objects
  $applicaitonList =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Perform any needed filtering based on passed variables
  foreach($file in $ApplicationConfigFiles){
    $json = Get-Content $file.FullName | ConvertFrom-Json
    # If specific parameters are passed, filter the list of applications based on those parameters
    if($GUID -and $json.GUID -ne $GUID){continue}
    if($appSource -and $json.appSource -ne $appSource){continue}
    if($IntuneAppName -and $json.IntuneAppName -notlike "$($IntuneAppName)"){continue}
    if($AppID -and $json.AppID -ne $AppID){continue}
    if($AppPublisher -and $json.AppPublisher -ne $AppPublisher){continue}
    if($AppFolderName -and $json.AppFolderName -ne $AppFolderName){continue}
    if($StorageAccountContainerName -and $json.StorageAccountContainerName -ne $StorageAccountContainerName){continue}
    if($archiveType -and $json.archiveType -ne $archiveType){continue}
    if($publishTo -and $publishTo -notin $json.publishTo){continue}
    if($public -and $json.publishTo.count -ne 0){continue}
    if($global:AppFactoryLogging){
      Write-PSFMessage -Message "Reading Application: <c='green'>$($json.IntuneAppName) ($($json.GUID))</c>"-Level $LogLevel -Tag "Application","$($json.IntuneAppName)" -Target "Application Factory Service"
    }
    $applicaitonList.Add($json) | Out-Null
  }
  # Return the application list
  return $applicaitonList
}
#EndRegion '.\Public\Get-AppFactoryApp.ps1' 109
#Region '.\Public\Get-AppFactoryAppConfiguration.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to return the application configuration for a specific application
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryAppConfiguration{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  # Get the application that we are working with
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel
  if (-not ($application)) {
    throw "Application with GUID $($GUID) does not exist."   
  }  
  # Read the configuration file for the application and then return it
  $ApplicationJSONPath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName,"App.json"
  $ApplicationJSON    = Get-Content -Path $ApplicationJSONPath | ConvertFrom-Json -Depth 5 
  return $ApplicationJSON
}
#EndRegion '.\Public\Get-AppFactoryAppConfiguration.ps1' 26
#Region '.\Public\Get-AppFactoryAppInstall.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to return the application install configuration
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryAppInstall {
  [CmdletBinding()]
  [OutputType([Hashtable])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"  
  )
  # Get the application that we will be working with
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel
  if (-not ($application)) {
    throw "Application with GUID $($GUID) does not exist."   
  }
  # Read the deploy-application.ps1 file
  $ApplicationDeploymentFilePath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName, "Deploy-Application.ps1"
  try {
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Reading application deployment file" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    }
    $ApplicationDeploymentFile = Get-Content -Path $ApplicationDeploymentFilePath
  }
  catch {
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    throw $_   
  }
  # Filter out to determine what type of the install is
  $type = ([Regex]::Match($ApplicationDeploymentFile, "#{3}INSTALLTYPE:(.+?)#{3}")).groups[1].value
  # Find if we have any processes that we check for at the beginning of the process
  $preStart = [Regex]::Match($ApplicationDeploymentFile, "###INSTALL:PRESTART###(.*)###INSTALL:PREEND###").groups[1].value
  $postStart = [Regex]::Match($ApplicationDeploymentFile, "###INSTALL:POSTSTART###(.*)###INSTALL:POSTEND###").groups[1].value
  $mainSection = [Regex]::Match($ApplicationDeploymentFile, "###INSTALLCMDSTART###(.*)###INSTALLCMDEND###").groups[1].value
  $beginstopProcess = ([Regex]::Matches($preStart, "(Get-Process -Name `"(.+?)`" | Stop-Process -Force)").Groups.Value | Where-Object { $_ -notlike "Get-Process*" -and $_ -notlike "*Stop-Process -Force" -and $_ -ne "" } | Sort-Object -Unique) -join ","
  $afterstopProcess = ([Regex]::Matches($postStart, "(Get-Process -Name `"(.+?)`" | Stop-Process -Force)").Groups.Value | Where-Object { $_ -notlike "Get-Process*" -and $_ -notlike "*Stop-Process -Force" -and $_ -ne "" } | Sort-Object -Unique) -join ","
  $installer = [Regex]::Match($mainSection, "\`$installer = `"(.+?)`"").groups[1].value
  if([Regex]::Match($mainSection, "\`$Parameters = `"`"").Success){
    $Parameters = ""
  }
  else{
    $Parameters = [Regex]::Match($mainSection, "\`$Parameters = `"(.+?)`"").groups[1].value
  }
  $ignoreExit = [Regex]::Match($mainSection, "-IgnoreExitCodes `"(.+?)`"").groups[1].value
  $mst = [Regex]::Match($mainSection, "\`$mstInstall = `"(.+?)`"").groups[1].value
  if ($type -eq "CUSTOM") {
    $startLine = $ApplicationDeploymentFile | Select-String -Pattern "###INSTALLCMDSTART###"
    $endLine = $ApplicationDeploymentFile | Select-String -Pattern "###INSTALLCMDEND###"
    $script = $ApplicationDeploymentFile[($ApplicationDeploymentFile.indexOf($startLine) + 1) .. ($ApplicationDeploymentFile.indexOf($endLine) - 1)]
  }
  $installCommands = @{
    Type             = $type
    beginstopProcess = $beginstopProcess
    afterstopProcess = $afterstopProcess
    installer        = $installer
    Parameters       = $Parameters 
    ignoreExit       = $ignoreExit
    WIM              = [Regex]::Match($mainSection, "Mount-WindowsImage -ImagePath \`$wimPath").Success
    mst              = $mst
    script           = $script
  }
  return $installCommands
}
#EndRegion '.\Public\Get-AppFactoryAppInstall.ps1' 68
#Region '.\Public\Get-AppFactoryAppOrgConfig.ps1' 0
function Get-AppFactoryAppOrgConfig{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$orgGUID,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$appGUID
  )
  $configFile = Join-Path -Path $script:AppFactorySourceDir -ChildPath "DSBConfigurations" -AdditionalChildPath $orgGUID,"$($appGUID).json"
  if(-not (Test-Path $configFile)){
    return $false
  }
  $config = Get-Content -Path $configFile | ConvertFrom-JSON
  return $config
}
#EndRegion '.\Public\Get-AppFactoryAppOrgConfig.ps1' 14
#Region '.\Public\Get-AppFactoryAppUninstall.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to return the application uninstall configuration
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryAppUninstall {
  [CmdletBinding()]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"  
  )  
  # Get the application that we will be working with
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel
  if (-not ($application)) {
    throw "Application with GUID $($GUID) does not exist."   
  }
  # Read the deploy-application.ps1 file
  $ApplicationDeploymentFilePath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName, "Deploy-Application.ps1"
  try {
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Reading application deployment file" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    }
    $ApplicationDeploymentFile = Get-Content -Path $ApplicationDeploymentFilePath
  }
  catch {
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    throw $_   
  }
  # Filter out to determine what type of the install is
  $type = ([Regex]::Match($ApplicationDeploymentFile, "#{3}UNINSTALLTYPE:(.+?)#{3}")).groups[1].value
  # Find if we have any processes that we check for at the beginning of the process
  $preStart = [Regex]::Match($ApplicationDeploymentFile, "###UNINSTALL:PRESTART###(.*)###UNINSTALL:PREEND###").groups[1].value
  $postStart = [Regex]::Match($ApplicationDeploymentFile, "###UNINSTALL:POSTSTART###(.*)###UNINSTALL:POSTEND###").groups[1].value
  $mainSection = [Regex]::Match($ApplicationDeploymentFile, "###UNINSTALLCMDSTART###(.*)###UNINSTALLCMDEND###").groups[1].value
  $beginstopProcess = ([Regex]::Matches($preStart, "(Get-Process -Name `"(.+?)`" | Stop-Process -Force)").Groups.Value | Where-Object { $_ -notlike "Get-Process*" -and $_ -notlike "*Stop-Process -Force" -and $_ -ne "" } | Sort-Object -Unique) -join ","
  $afterstopProcess = ([Regex]::Matches($postStart, "(Get-Process -Name `"(.+?)`" | Stop-Process -Force)").Groups.Value | Where-Object { $_ -notlike "Get-Process*" -and $_ -notlike "*Stop-Process -Force" -and $_ -ne "" } | Sort-Object -Unique) -join ","
  $installer = [Regex]::Match($mainSection, "\`$installer = `"(.+?)`"").groups[1].value
  if ([Regex]::Match($mainSection, "\`$Parameters = `"`"").Success) {
    $Parameters = ""
  }
  else {
    $Parameters = [Regex]::Match($mainSection, "\`$Parameters = `"(.+?)`"").groups[1].value
  }
  $ignoreExit = [Regex]::Match($mainSection, "-IgnoreExitCodes `"(.+?)`"").groups[1].value
  $name = [Regex]::Match($mainSection, "Remove-MSIApplications -Name `"(.+?)`"").groups[1].value
  $MSIGUID = [Regex]::Match($mainSection, "Execute-MSI -Action `"Uninstall`" -Path `"(.+?)`"").groups[1].value
  if ($type -eq "CUSTOM") {
    $startLine = $ApplicationDeploymentFile | Select-String -Pattern "###UNINSTALLCMDSTART###"
    $endLine = $ApplicationDeploymentFile | Select-String -Pattern "###UNINSTALLCMDEND###"
    $script = $ApplicationDeploymentFile[($ApplicationDeploymentFile.indexOf($startLine) + 1) .. ($ApplicationDeploymentFile.indexOf($endLine) - 1)]
  }  
  $uninstallCommands = @{
    Type             = $type
    beginstopProcess = $beginstopProcess
    afterstopProcess = $afterstopProcess
    installer        = $installer
    Parameters       = $Parameters 
    ignoreExit       = $ignoreExit
    WIM              = [Regex]::Match($mainSection, "Mount-WindowsImage -ImagePath \`$wimPath").Success
    Name             = $name
    MSIGUID          = $MSIGUID
    dirFiles         = [Regex]::Match($mainSection, "\`$\(\`$dirFiles\)").Success
    script           = $script
  }
  return   $uninstallCommands
}
#EndRegion '.\Public\Get-AppFactoryAppUninstall.ps1' 70
#Region '.\Public\Get-AppFactoryInstaller.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to download the app installers that we will be packaging from their appropriate sources
  .PARAMETER applicationList
  The collection of the applications that we will be acting on
  .PARAMETER multithread
  Show we proceed single threaded or multithreaded
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryInstaller {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter()][ValidateNotNullOrEmpty()][switch]$multithread,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"    
  )
  # Create list to store results of the process
  $applications = [System.Collections.Generic.List[PSCustomObject]]@()
  # Connect to Azure Storage Context
  $appStorageContext = Connect-AppFactoryAzureStorage -storageContainer $script:AppFactoryInstallersContainer -storageSecret $script:AppFactoryInstallersSecret -LogLevel $LogLevel
  # Details to conect to Sharepoint if required
  $pfxPath = Join-Path -Path $script:AppFactoryLocalSupportFiles -ChildPath $script:AppFactorySharepointCertificate
  $sharepointConfig = @{
    "url"                 = $script:AppFactorySharrepointURL
    "CertificatePath"     = $pfxPath
    "CertificatePassword" = $script:AppFactorySharepointCertificateSecret.Password
    "ClientId"            = $script:AppFactorySharepointClientID
    "Tenant"              = $script:AppFactorySharepointTenant
  }
  if (-not $multithread.IsPresent) {
    try { Connect-PnPOnline @sharepointConfig }
    catch {}    
    foreach ($application in $applicationList) {
      try {
        $AppSetupFolderPath = Get-AppFactoryInstallerFiles -application $application -appStorageContext $appStorageContext -workspace $script:AppFactoryWorkspace -supportFiles $script:AppFactorySupportFiles -LogLevel $LogLevel
        $application | Add-Member -MemberType NoteProperty -Name 'AppSetupFolderPath' -Value $AppSetupFolderPath
        $applications.add($application) | Out-Null
      }
      catch {
        if ($script:AppFactoryLogging) {
          Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Unable to download application files</c>"   -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "Application Factory Service"    
          Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Removing from application list</c>"   -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "Application Factory Service"
          Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "Application Factory Service"
        }
        throw $_
      }
    }
  }
  else {
    $applist = [hashtable]::Synchronized(@{})
    if ($script:LocalModulePath) {
      $modulePath = $script:LocalModulePath
    }
    else {
      $modulePath = (Get-module ApplicationFactoryService).path
    }
    $applicationList | Foreach-Object -Parallel {
      Import-Module -Name $using:modulePath -Force
      $application = $_
      $dict = $using:applist
      $appStorageContext = $using:appStorageContext
      $LogLevel = $using:LogLevel
      $script:AppFactoryLogging = $using:script:AppFactoryLogging
      $script:AppFactoryWorkspace = $using:script:AppFactoryWorkspace
      $script:AppFactorySupportFiles = $using:script:AppFactorySupportFiles
      if ($application.AppSource -eq "Sharepoint") {
        Connect-PnPOnline @using:sharepointConfig
      }  
      try {
        $AppSetupFolderPath = Get-AppFactoryInstallerFiles -application $application -appStorageContext $appStorageContext -workspace $script:AppFactoryWorkspace -supportFiles $script:AppFactorySupportFiles -LogLevel $LogLevel
        $obj = [PSCustomObject]@{
          "Status"             = "Downloaded"
          "Error"              = ""
          "AppSetupFolderPath" = $AppSetupFolderPath 
        }
        $dict.add($application.GUID, $obj)        
      }
      catch {
        if ($global:AppFactoryLogging) {
          Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Unable to download application files</c>"   -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "AppFactory"      
          Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Removing from application list</c>"   -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "AppFactory"
        }        
      }    
    }
    foreach ($item in $applist.GetEnumerator()) {
      if ($item.Value.status -eq "Downloaded") {
        $index = $applicationList.guid.indexOf($item.key)
        $application = $applicationList[$index]
        $application | Add-Member -MemberType NoteProperty -Name 'AppSetupFolderPath' -Value $item.Value.AppSetupFolderPath
        $applications.Add($application) | Out-Null
      }
    }      
  }
  return $applications
}
#EndRegion '.\Public\Get-AppFactoryInstaller.ps1' 98
#Region '.\Public\Get-AppFactoryOrganization.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to list out the organizations that are configured
  .PARAMETER GUID
  The unique identifer for the specific organization
  .PARAMETER Name
  Get an organization based on the name
  .PARAMETER Contact
  Get organizations that have a specific contact
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
 
  .EXAMPLE
  Get all organizations
    Get-AppFactoryOrganization
 
  Get specific organization by GUID
    Get-AppFactoryOrganization -GUID "### GUID ###"
 
  Get specific organization by Name
    Get-AppFactoryOrganization -Name "### Organization Name ###"
 
  Get specific organization by Contact
    Get-AppFactoryOrganization -Contact "### Contact Name ###"
#>

function Get-AppFactoryOrganization {
  [CmdletBinding(DefaultParameterSetName = 'All')]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $false, ParameterSetName = 'GUID')][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter(Mandatory = $false, ParameterSetName = 'Name')][ValidateNotNullOrEmpty()][string]$Name,
    [Parameter(Mandatory = $false, ParameterSetName = 'Contacts')][ValidateNotNullOrEmpty()][string]$Contact,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Organization Folder Path
  $orgainizationFolder = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Organizations"
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "Reading organization files in path $($orgainizationFolder)" -Level $LogLevel -Tag "Organizations" -Target "Application Factory Service"
  }
  # Get list of organizational configuration files
  $organizationConfigs = Get-Childitem -Path $orgainizationFolder
  # Create list that we will return with the organizational configurations
  $orgList = [System.Collections.Generic.List[PSCustomObject]]@()
  foreach ($file in $organizationConfigs) {
    # Read the organizational file
    $json = Get-Content $file.FullName | ConvertFrom-Json
    if ($GUID -and $json.GUID -ne $GUID) { continue }
    # If Name is set, match the name variable.
    if ($Name -and $json.Name -notlike "$($Name)") { continue }
    # If Contact is set, match entries that have that contact
    if ($Contact -and $Json.Contacts.indexOf($contact) -eq -1) { continue }
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "Reading organization file $($file)" -Level $LogLevel -Tag "Organizations", "$($json.Name)" -Target "Application Factory Service"
    }
    # Add the path to the file for specific organization
    $json | Add-Member -MemberType "NoteProperty" -Name "FileName" -Value $file.Name
    $orgList.Add($json) | Out-Null
  }
  # Return the organiation list
  return $orgList
}
#EndRegion '.\Public\Get-AppFactoryOrganization.ps1' 62
#Region '.\Public\Get-AppFactoryOrganizationsAppList.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to list out the applications that are available to a specific organization.
  .PARAMETER organization
  The unique identifer for the specific organization
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactoryOrganizationsAppList{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$organization,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  # Blank List for the Apps
  $applicaitonList =  [System.Collections.Generic.List[PSCustomObject]]@()
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "Getting Applications for $($organization)" -Level $LogLevel -Tag "Application","Organization" -Target "Application Factory Service"
  }
  # Read DSB Configuration Files
  $dsbConfigPath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "DSBConfigurations" -AdditionalChildPath $organization
  $dsbConfigFiles = Get-ChildItem -Path $dsbConfigPath -File -Filter "*.json" -ErrorAction SilentlyContinue
  # Get all applications that are set as public i.e. no specific organization
  $publicApps = Get-AppFactoryApp -public -LogLevel $LogLevel
  foreach($app in $publicApps){
    if($null -eq $app.Version){continue}
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "Adding application to app list: <c='green'>$($app.IntuneAppName) ($($app.GUID))</c>" -Level $LogLevel -Tag "Application","Organization" -Target "Application Factory Service"
    }
    $appConfigFile = $dsbConfigFiles | Where-Object {$_.name -eq "$($app.GUID).json"}
    if($appConfigFile){
      $obj = Get-Content -Path $appConfigFile | ConvertFrom-JSON -Depth 5
      $obj | Add-Member -MemberType NoteProperty -Name "GUID" -Value $app.GUID
      $obj | Add-Member -MemberType NoteProperty -Name "IntuneAppName" -Value $app.IntuneAppName
      $obj | Add-Member -MemberType NoteProperty -Name "AppVersion" -Value $app.version
      $obj | Add-Member -MemberType NoteProperty -Name "container" -Value $script:AppFactoryPublicFolder
    }
    else{
      $obj = [PSCustomObject]@{
        "AddToIntune"             = $false
        "AvailableAssignments"    = @()
        "AvailableExceptions"     = @()
        "RequiredAssignments"     = @()
        "RequiredExceptions"      = @()
        "UninstallAssignments"    = @()
        "UninstallExceptions"     = @()
        "UnassignPrevious"        = $true
        "CopyPrevious"            = $true
        "KeepPrevious"            = 0
        "foreground"              = $false
        "filters"                 = ""
        "espprofiles"             = @()
        "container"               = $script:AppFactoryPublicFolder
        "GUID"                    = $app.GUID
        "IntuneAppName"           = $app.IntuneAppName
        "AppVersion"              = $app.version
        "InteractiveInstall"      = $false
        "InteractiveUninstall"    = $false
      }
    }
    $applicaitonList.Add($obj) | Out-Null  
  }
  # Get all applications that are set to be published to a specific organization
  $orgApps = Get-AppFactoryApp -publishTo $organization
  foreach($app in $orgApps){
    if($null -eq $app.Version){continue}
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "Adding application to app list: <c='green'>$($app.IntuneAppName) ($($app.GUID))</c>" -Level $LogLevel -Tag "Application","Organization" -Target "Application Factory Service"
    }
    $appConfigFile = $dsbConfigFiles | Where-Object {$_.name -eq "$($app.GUID).json"}
    if($appConfigFile){
      $obj = Get-Content -Path $appConfigFile | ConvertFrom-JSON -Depth 5
      $obj | Add-Member -MemberType NoteProperty -Name "GUID" -Value $app.GUID
      $obj | Add-Member -MemberType NoteProperty -Name "IntuneAppName" -Value $app.IntuneAppName
      $obj | Add-Member -MemberType NoteProperty -Name "AppVersion" -Value $app.version    
      $obj | Add-Member -MemberType NoteProperty -Name "container" -Value "organization"
    }
    else{
      $obj = [PSCustomObject]@{
        "AddToIntune"             = $false
        "AvailableAssignments"    = @()
        "AvailableExceptions"     = @()
        "RequiredAssignments"     = @()
        "RequiredExceptions"      = @()
        "UninstallAssignments"    = @()
        "UninstallExceptions"     = @()
        "UnassignPrevious"        = $true
        "CopyPrevious"            = $true
        "KeepPrevious"            = 0
        "foreground"              = $false
        "filters"                 = ""
        "espprofiles"             = @()
        "container"               = "organization"
        "GUID"                    = $app.GUID
        "IntuneAppName"           = $app.IntuneAppName
        "AppVersion"              = $app.version    
        "InteractiveInstall"      = $false
        "InteractiveUninstall"    = $false            
      }
    }
    $applicaitonList.Add($obj) | Out-Null  
  }   
  return $applicaitonList
}
#EndRegion '.\Public\Get-AppFactoryOrganizationsAppList.ps1' 106
#Region '.\Public\Get-AppFactorySASToken.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to generate a time bound SAS token for a specific organization to download application files
  .PARAMETER GUID
  The unique identifer for the specific organization
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Get-AppFactorySASToken{
  [CmdletBinding()]
  [OutputType([Hashtable])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter()][ValidateNotNullOrEmpty()][int]$hours = 2,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  # Create expiry time
  $SASExpiry = (Get-Date).ToUniversalTime().AddHours($hours)
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "Getting SAS for $($GUID) to access applications files" -Level $LogLevel -Tag "Organization","Azure Storage","SAS","$($GUID)" -Target "Application Factory Service"
    Write-PSFMessage -Message "SAS Token will expire at $($SASExpiry)" -Level $LogLevel -Tag "Organization","Azure Storage","SAS","$($GUID)" -Target "Application Factory Service"
    Write-PSFMessage -Message "Connecting to Azure Storage"  -Level $LogLevel -Tag "Application","AzureStorage" -Target "Application Factory Service"
  }
  # Generate azure storage context
  $appStorageContext = Connect-AppFactoryAzureStorage -storageContainer $script:AppFactoryDeploymentsContainer -storageSecret $script:AppFactoryDeploymentsSecret -LogLevel $LogLevel
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "Creating SAS Tokens"  -Level $LogLevel -Tag "Application","AzureStorage" -Target "Application Factory Service"
  }
  # Store the details for the connectivity and return it
  $SASTokens = @{
    "StoragePath" = "https://$($script:AppFactoryDeploymentsContainer).blob.core.windows.net"
    "PublicContainerName" = $script:AppFactoryPublicFolder
    "OrganizationContainerName" = $GUID
    "public" = New-AzStorageContainerSASToken -Context $appStorageContext -Name $script:AppFactoryPublicFolder -Permission r  -ExpiryTime $SASExpiry
    "organization" = New-AzStorageContainerSASToken -Context $appStorageContext -Name $GUID -Permission r -ExpiryTime $SASExpiry    
  }
  return $SASTokens
}
#EndRegion '.\Public\Get-AppFactorySASToken.ps1' 39
#Region '.\Public\New-AppFactoryApp.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to create a new application to be handled by the automated process
  .PARAMETER Name
  The name of the application that we are creating
  .PARAMETER AppSource
  The source for the application files that will be used
  .PARAMETER InformationURL
  The URL for more info for the application
  .PARAMETER PrivacyURL
  The URL for privacy information for the application
  .PARAMETER publishTo
  The list of organizations that this should be published to if not public
  .PARAMETER force
  Continue even if the folder already exists, overwriting the current details
  .PARAMETER archiveType
  If the download for the installer is in a archive of some form
  .PARAMETER filterOptions
  Filter options for evergreen based applications
  .PARAMETER appSetupName
  The application setup name that is used to run the install
  .PARAMETER storageContainerName
  The storage container holding the installer files for azure based installs
  .PARAMETER appID
  The application id used in evergreen and winget install process
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
 
  .EXAMPLE
   
  Create a new application that is based on an azure storage account
  $parameters = @{
    Name = "### Name ###"
    Publisher = "### Publisher ###"
    AppSource = "StorageAccount"
    InformationURL = "### URL ###"
    PrivacyURL = "### URL ###"
    appSetupName = "### Setup Name ###"
    storageContainerName = "### Container Name ###"
    LogLevel = "Output"
  }
  New-AppFactoryApp @parameters
 
  Create a new application that is based on a sharepoint folder
  $parameters = @{
    Name = "### Name ###"
    Publisher = "### Publisher ###"
    AppSource = "Sharepoint"
    InformationURL = "### URL ###"
    PrivacyURL = "### URL ###"
    appSetupName = "### Setup Name ###"
    LogLevel = "Output"
  }
  New-AppFactoryApp @parameters
 
  Create a new application that is based on a evergreen source
  $parameters = @{
    Name = "### Name ###"
    Publisher = "### Publisher ###"
    AppSource = "Evergreen"
    InformationURL = "### URL ###"
    PrivacyURL = "### URL ###"
    filterOptions = @{}
    appID = "### App ID ###"
    appSetupName = "### Setup Name ###"
    LogLevel = "Output"
  }
  New-AppFactoryApp @parameters
 
  Create a new application that is based on a winget source
  $parameters = @{
    Name = "### Name ###"
    Publisher = "### Publisher ###"
    AppSource = "Winget"
    InformationURL = "### URL ###"
    PrivacyURL = "### URL ###"
    appID = "### App ID ###"
    appSetupName = "### Setup Name ###"
    LogLevel = "Output"
  }
  New-AppFactoryApp @parameters
 
  Create a new application that is based on PSADT
  $parameters = @{
    Name = "### Name ###"
    Publisher = "### Publisher ###"
    AppSource = "PSADT"
    InformationURL = "### URL ###"
    PrivacyURL = "### URL ###"
    LogLevel = "Output"
  }
  New-AppFactoryApp @parameters
 
  Create an application that is based on an ECNO install
  $parameters = @{
    Name = "### Name ###"
    Publisher = "### Publisher ###"
    AppSource = "ECNO"
    InformationURL = "### URL ###"
    PrivacyURL = "### URL ###"
    storageContainerName = "### Folder Name ###"
    LogLevel = "Output"
  }
  New-AppFactoryApp @parameters
#>

function New-AppFactoryApp {
  [CmdletBinding(DefaultParameterSetName = 'PSADT')]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$Name,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$Publisher,
    [Parameter(Mandatory = $true)][ValidateSet("StorageAccount", "Sharepoint", "Winget", "Evergreen", "PSADT", "ECNO")][String]$AppSource,
    [Parameter()][string]$InformationURL,
    [Parameter()][string]$PrivacyURL,
    [Parameter()][String[]]$publishTo = @(),
    [Parameter()][switch]$force,
    [Parameter()][ValidateSet("WIM", "7Z", "ZIP", "MSIZIP", "EXEZIP", "PSZIP", $null)][string]$archiveType = $null,
    [Parameter()][Hashtable]$filterOptions = @{},
    [Parameter(Mandatory = $true, ParameterSetName = 'AzureStorage')]
    [Parameter(Mandatory = $true, ParameterSetName = 'Sharepoint')]
    [Parameter(Mandatory = $true, ParameterSetName = 'WingetEvergreen')]
    [string]$appSetupName = $null,
    [Parameter(Mandatory = $true, ParameterSetName = 'ECNO')]
    [Parameter(Mandatory = $true, ParameterSetName = 'AzureStorage')]
    [string]$storageContainerName = $null,
    [Parameter(Mandatory = $true, ParameterSetName = 'WingetEvergreen')]
    [string]$appID = $null,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($Name)] Creating application" -Level $LogLevel -Tag "Application", "$($Name)" -Target "Application Factory Service"
  }
  # Create the package folders for the application we are creating
  $appFolderPath = New-AppFactoryAppFolder -Name $name -LogLevel $LogLevel -Force $force
  # Create the application configuration file for the application we are creating
  $ApplicationConfig = [PSCustomObject]@{
    "GUID"                        = (New-GUID).Guid
    "IntuneAppName"               = "STS-$($Name)"
    "AppPublisher"                = $Publisher
    "InformationURL"              = $InformationURL
    "PrivacyURL"                  = $PrivacyURL
    "AppSource"                   = $AppSource
    "AppID"                       = $appID
    "AppFolderName"               = $Name
    "AppSetupFileName"            = $appSetupName
    "StorageAccountContainerName" = $storageContainerName
    "archiveType"                 = $archiveType
    "FilterOptions"               = $filterOptions
    "publishTo"                   = $publishTo
    "version"                     = $null
  }
  # Create the configuration for the application
  try{
    if($global:AppFactoryLogging){
      Write-PSFMessage -Message "[$($Name)] Writing default app configuration" -Level $LogLevel -Tag "Application","$($Name)" -Target "AppFactory" 
    }
    $ApplicationConfigFile = Join-Path -Path $appFolderPath -ChildPath "config.json"
    $ApplicationConfig | ConvertTo-Json | Out-File -FilePath $ApplicationConfigFile
  }
  catch{
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($Name)" -Target "Application Factory Service"
    throw $_ 
  }
}
#EndRegion '.\Public\New-AppFactoryApp.ps1' 164
#Region '.\Public\New-AppFactoryIntuneFile.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to create a new application to be handled by the automated process
  .PARAMETER applicationList
  The collection of the applications that we will be acting on
  .PARAMETER multithread
  Show we proceed single threaded or multithreaded
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function New-AppFactoryIntuneFile {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter()][ValidateNotNullOrEmpty()][switch]$multithread,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"  
  )
  # List of objects and their status through the process
  $applications = [System.Collections.Generic.List[PSCustomObject]]@()
  if (-not $multithread.IsPresent) {
    foreach ($application in $applicationList) {
      try {
        $app = New-AppFactoryIntuneWin -application $application -LogLevel $LogLevel
        $applications.add($app)
      }
      catch {
        throw $_
      }
    }
  }
  else {
    # Generate intune application file
    $applist = [hashtable]::Synchronized(@{}) 
    if ($script:LocalModulePath) {
      $modulePath = $script:LocalModulePath
    }
    else {
      $modulePath = (Get-module ApplicationFactoryService).path
    }
    $applicationList | Foreach-Object -Parallel {
      Import-Module -Name $using:modulePath -Force
      try {
        $application = $_
        $dict = $using:applist
        $script:AppFactoryLogging = $using:script:AppFactoryLogging
        $LogLevel = $using:LogLevel
        $app = New-AppFactoryIntuneWin -application $application -LogLevel $LogLevel 
        $obj = [PSCustomObject]@{
          "Status"      = "Good"
          "Application" = $app 
          "Error"       = $null
        }
        $dict.add($application.GUID, $obj)        
      }
      catch {
        $obj = [PSCustomObject]@{
          "Status"               = "Fail"
          "AppPublishFolderPath" = $null
          "Error"                = $_
        }
        $dict.add($application.GUID, $obj)
      }      
    }
    foreach ($item in $applist.GetEnumerator()) {
      if ($item.Value.status -eq "Good") {
        $applications.Add($item.Value.Application) | Out-Null
      }
    }
  }
  return $applications
}
#EndRegion '.\Public\New-AppFactoryIntuneFile.ps1' 73
#Region '.\Public\New-AppFactoryOrganization.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to create a new organization
  .PARAMETER name
  The name of the organization
  .PARAMETER contactList
  The list of contacts for the organization
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
 
  .EXAMPLE
 
  Create a new organization
    New-AppFactoryOrganization -name "### Organization Name ###"
 
  Create a new organization with contacts
    New-AppFactoryOrganization -name "### Organization Name ###" -contactList "### Contact 1 ###", "### Contact 2 ###"
#>

function New-AppFactoryOrganization {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$name,
    [Parameter()][string[]]$contactList = @(),
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"      
  )
  # Generate a unique GUID for organization
  $GUID = (New-GUID).Guid
  # Create the Organization file to write out
  $obj = [PSCustomObject]@{
    "GUID"     = $GUID
    "Name"     = $name
    "Contacts" = $contactList
  }
  # Path for the new organization file
  $orgFile = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Organizations" -AdditionalChildPath "$($name).json"
  # Organization File Path
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "Creating organization GUID: <c='green'>$($GUID)</c>" -Level $LogLevel -Tag "Organizations", "$($Name)" -Target "Application Factory Service"
    Write-PSFMessage -Message "Organization file path: <c='green'>$($orgFile)</c>" -Level $LogLevel -Tag "Organizations", "$($Name)" -Target "Application Factory Service"
  }
  # First write out the organization json
  try {
    if (Test-Path -Path $orgFile) {
      throw "Organization already exists with this name."
    }
    $obj | ConvertTo-Json | Out-File -FilePath $orgFile -Force
  }
  catch {
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Organizations" -Target "Application Factory Service"
    throw $_
  }
  # Create Azure Storage Container to store organization specific applications
  try {
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "Creating organization storage container $($GUID)" -Level $LogLevel -Tag "Organizations", "$($Name)" -Target "AppFactory"
    }
    $orgStorageContainerContext = Connect-AppFactoryAzureStorage -storageContainer $script:AppFactoryDeploymentsContainer -storageSecret $script:AppFactoryDeploymentsSecret
    New-AzStorageContainer -Name $GUID -Permission Off -Context $orgStorageContainerContext | Out-Null
  }
  catch {
    Remove-Item -Path $orgFile -Force
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Organizations" -Target "Application Factory Service"
    throw $_
  }
}
#EndRegion '.\Public\New-AppFactoryOrganization.ps1' 66
#Region '.\Public\Publish-AppFactoryApp.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to publish the files that were created for the deployment
  .PARAMETER applicationList
  The collection of the applications that we will be acting on
  .PARAMETER multithread
  Show we proceed single threaded or multithreaded
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Publish-AppFactoryApp{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"  
  )
  # Create list for application status
  $applications = [System.Collections.Generic.List[PSCustomObject]]@()
  # Create azure storage context to use during the process
  $appStorageContext = Connect-AppFactoryAzureStorage -storageContainer $script:AppFactoryDeploymentsContainer -storageSecret $script:AppFactoryDeploymentsSecret -LogLevel $LogLevel
  foreach($application in $applicationList){
    try{
      $app = Add-AppFactoryIntuneDeployment -application $application -appStorageContext $appStorageContext -AppFactoryPublicFolder $script:AppFactoryPublicFolder -LogLevel $LogLevel
      $applications.add($app)
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($application.IntuneAppName)] Published update for application" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }        
    }
    catch{
      throw $_
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($application.IntuneAppName)] Failed to update application" -Level "Error" -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }        
    }
  }
  return $applications
}
#EndRegion '.\Public\Publish-AppFactoryApp.ps1' 39
#Region '.\Public\Remove-AppFactoryApp.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to remove an application from the process of packaging
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Remove-AppFactoryApp{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  #Get App Factory Information
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel  
  if(-not ($application)){
    throw "Application with GUID $($GUID) does not exist." 
  }
  # Get the application folder
  $ApplicationFolder = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($app.IntuneAppName)] Removing Configuration Files at path $($ApplicationFolder)" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  # Remove the folder
  try{
    Remove-Item -Path $ApplicationFolder -Recurse -Force | Out-Null
  }
  catch{
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
    throw $_  
  }
}
#EndRegion '.\Public\Remove-AppFactoryApp.ps1' 34
#Region '.\Public\Remove-AppFactoryOrganization.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to remove an organization from the process of packaging
  .PARAMETER GUID
  The unique identifier for the organization that we want to work with
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
 
  .EXAMPLE
 
  Remove an organization
    Remove-AppFactoryOrganization -GUID "### GUID ###"
#>

function Remove-AppFactoryOrganization{
  [CmdletBinding()]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"  
  ) 
  # Get Org Configuration Files
  $organization = Get-AppFactoryOrganization -GUID $GUID -LogLevel $LogLevel
  if ($null -eq $organization) {
    Write-PSFMessage -Message "Error Encountered: Organization not found." -Level "Error" -Tag "Organizations" -Target "Application Factory Service"
    throw "Organization not found." 
  }
  if($script:AppFactoryLogging){
    Write-PSFMessage -Message "[$($organization.Name)] Removing organization GUID: <c='green'>$($GUID)</c> with name <c='green'>$($organization.Name)</c>)" -Level $LogLevel -Tag "Organizations","$($organization.Name)" -Target "Application Factory Service"
  }
  try{
    $orgStorageContainerContext = Connect-AppFactoryAzureStorage -storageContainer $script:AppFactoryDeploymentsContainer -storageSecret $script:AppFactoryDeploymentsSecret
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($organization.Name)] Removing Azure Container" -Level $LogLevel -Tag "Organizations","$($organization.Name)" -Target "Application Factory Service"
    }
    # Remove Azure Storage Container
    Remove-AzStorageContainer -Name $GUID -Context $orgStorageContainerContext -Force -ErrorAction Stop 
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($organization.Name)] Removing configuration file" -Level $LogLevel -Tag "Organizations","$($organization.Name)" -Target "Application Factory Service"
    }
    # Remove Configuration File.
    $fileName = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Organizations" -AdditionalChildPath $organization.fileName
    Remove-Item -Path $fileName -Force
  }
  catch{
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Organizations" -Target "Application Factory Service"
    throw $_
  }
}
#EndRegion '.\Public\Remove-AppFactoryOrganization.ps1' 48
#Region '.\Public\Remove-AppFactoryWorkspaceFiles.ps1' 0
function Remove-AppFactoryWorkspaceFiles{
  [CmdletBinding()]
  param(  
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"  
  )
  Write-PSFMessage -Message "[Application Factory Service] Cleaning up workspace folders <c='green'>$($script:AppFactoryWorkspace)</c>" -Level $LogLevel -Tag "Organizations","$($organization.Name)" -Target "Application Factory Service"
  if(Test-Path -Path $script:AppFactoryWorkspace){
    Remove-Item -Path $script:AppFactoryWorkspace -Force -Recurse
  }
}
#EndRegion '.\Public\Remove-AppFactoryWorkspaceFiles.ps1' 11
#Region '.\Public\Set-AppFactoryAppConfiguration.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to update the confirmation files for an application
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER Description
  The description of the application
  .PARAMETER Notes
  Any notes for the application
  .PARAMETER Owner
  Who is the owner of the application
  .PARAMETER InstallExperience
  Who is the owner of the application
  .PARAMETER DeviceRestartBehavior
  What the device should do on restart
  .PARAMETER AllowAvailableUninstall
  If should be available to uninstall in company portal if installed as available
  .PARAMETER MinimumSupportedWindowsRelease
  Minimum version of windows supported
  .PARAMETER Architecture
  Required OS architecture
  .PARAMETER IntuneAppName
  Application name
  .PARAMETER AppPublisher
  Application Publisher of the application
  .PARAMETER InformationURL
  Info URL for more application information
  .PARAMETER PrivacyURL
  Privacy URL for more application privacy information
  .PARAMETER AppSource
  Where the application files are being sourced from
  .PARAMETER AppID
  AppID used in winget and evergreen processes
  .PARAMETER AppSetupFileName
  The setup file that will be run as part of the install
  .PARAMETER StorageAccountContainerName
  The storage container that the installers are stored in for azure based process
  .PARAMETER archiveType
  If the file downloaded is an archive and needs to be treated as such
  .PARAMETER FilterOptions
  Filter options for evergreen applications
  .PARAMETER publishTo
  What organizations should be published for
  .PARAMETER version
  Version of the application
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Set-AppFactoryAppConfiguration {
  [CmdletBinding()]
  param( 
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter()][string]$Description,
    [Parameter()][string]$Notes,
    [Parameter()][string]$Owner,
    [Parameter()][ValidateSet("system", "user")][string]$InstallExperience,
    [Parameter()][ValidateSet("suppress", "force", "basedOnReturnCode", "allow")][string]$DeviceRestartBehavior,
    [Parameter()][ValidateSet("true", "false")][string]$AllowAvailableUninstall,
    [Parameter()][ValidateSet("W10_1607", "W10_1703", "W10_1709", "W10_1809", "W10_1909", "W10_2004", "W10_20H2", "W10_21H1", "W10_21H2", "W10_22H2", "W11_21H2", "W11_22H2")][string]$MinimumSupportedWindowsRelease,
    [Parameter()][ValidateSet("All", "x64", "x86")][string]$Architecture,
    [Parameter()][ValidateNotNullOrEmpty()][string]$IntuneAppName,
    [Parameter()][ValidateNotNullOrEmpty()][string]$AppPublisher,
    [Parameter()][string]$InformationURL,
    [Parameter()][string]$PrivacyURL,
    [Parameter()][ValidateSet("StorageAccount", "Evergreen", "PSADT", "Sharepoint", "Winget")][string]$AppSource,
    [Parameter()][string]$AppID,
    [Parameter()][string]$AppSetupFileName,
    [Parameter()][string]$StorageAccountContainerName,
    [Parameter()][ValidateSet("WIM", "7Z", "ZIP", "MSIZIP", "EXEZIP", "PSZIP", $null)][string]$archiveType,
    [Parameter()][Hashtable]$FilterOptions,
    [Parameter()][string[]]$publishTo,
    [Parameter()][string]$version,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"  
  )
  # Check if the application that we are trying to update actually exist
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel
  if (-not ($application)) {
    throw "Application with GUID $($GUID) does not exist."
  }
  # Get Config File paths that might need to be updated
  $ApplicationJSONPath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName, "App.json"
  $ApplicationConfigPath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName, "Config.json"
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Reading current <c='green'>$($ApplicationJSONPath)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Reading current <c='green'>$($ApplicationConfigPath)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }
  try {
    $ApplicationJSON = Get-Content -Path $ApplicationJSONPath | ConvertFrom-Json -Depth 5
    $ApplicationConfig = Get-Content -Path $ApplicationConfigPath | ConvertFrom-Json -Depth 5
  }
  catch {
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Organizations" -Target "Application Factory Service"
    throw $_    
  }
  # What files have we updated
  $ApplicationJSONUpdated = $false
  $ApplicationConfigUpdated = $false
  switch ($PSBoundParameters.Keys) {
    "Description" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - Description - <c='green'>$($Description)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON.Information.Description = $Description
      $ApplicationJSONUpdated = $true
    }
    "Notes" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - Notes - <c='green'>$($Notes)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON.Information.Notes = $Notes
      $ApplicationJSONUpdated = $true      
    }
    "Owner" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - Owner - <c='green'>$($Owner)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON.Information.Owner = $Owner
      $ApplicationJSONUpdated = $true        
    }
    "InstallExperience" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - Install Experience - <c='green'>$($InstallExperience)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON.Program.InstallExperience = $InstallExperience
      $ApplicationJSONUpdated = $true          
    }
    "DeviceRestartBehavior" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - Device Restart Behaviour - <c='green'>$($DeviceRestartBehavior)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON.Program.DeviceRestartBehavior = $DeviceRestartBehavior
      $ApplicationJSONUpdated = $true        
    }
    "AllowAvailableUninstall" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - Allow Available Uninstall - <c='green'>$($AllowAvailableUninstall)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON.Program.AllowAvailableUninstall = $AllowAvailableUninstall
      $ApplicationJSONUpdated = $true        
    }
    "MinimumSupportedWindowsRelease" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - Minimum Supported WIndows - <c='green'>$($MinimumSupportedWindowsRelease)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON.RequirementRule.MinimumSupportedWindowsRelease = $MinimumSupportedWindowsRelease
      $ApplicationJSONUpdated = $true          
    }
    "Architecture" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - Architecture - <c='green'>$($Architecture)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON.RequirementRule.Architecture = $Architecture
      $ApplicationJSONUpdated = $true        
    }
    "IntuneAppName" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - IntuneAppName - <c='green'>$($IntuneAppName)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.IntuneAppName = $IntuneAppName
      $ApplicationConfigUpdated = $true           
    }
    "AppPublisher" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - AppPublisher - <c='green'>$($AppPublisher)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.AppPublisher = $AppPublisher
      $ApplicationConfigUpdated = $true           
    }
    "InformationURL" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - InformationURL - <c='green'>$($InformationURL)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.InformationURL = $InformationURL
      $ApplicationConfigUpdated = $true        
    }
    "PrivacyURL" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - PrivacyURL - <c='green'>$($PrivacyURL)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.PrivacyURL = $PrivacyURL
      $ApplicationConfigUpdated = $true         
    }
    "AppSource" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - AppSource - <c='green'>$($AppSource)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.AppSource = $AppSource
      $ApplicationConfigUpdated = $true           
    }
    "AppID" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - AppID - <c='green'>$($AppID)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.AppID = $AppID
      $ApplicationConfigUpdated = $true        
    }
    "AppSetupFileName" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - AppSetupFileName - <c='green'>$($AppSetupFileName)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.AppSetupFileName = $AppSetupFileName
      $ApplicationConfigUpdated = $true       
    }
    "StorageAccountContainerName" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - StorageAccountContainerName - <c='green'>$($StorageAccountContainerName)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.StorageAccountContainerName = $StorageAccountContainerName
      $ApplicationConfigUpdated = $true       
    }
    "archiveType" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - archiveType - <c='green'>$($archiveType)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.archiveType = $archiveType
      $ApplicationConfigUpdated = $true       
    }
    "FilterOptions" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - FilterOptions - <c='green'>$($FilterOptions)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.FilterOptions = $FilterOptions
      $ApplicationConfigUpdated = $true      
    }
    "publishTo" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - publishTo - <c='green'>$($publishTo)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.publishTo = $publishTo
      $ApplicationConfigUpdated = $true
    }
    "version" {
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating - version - <c='green'>$($version)</c>" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig.version = $version
      $ApplicationConfigUpdated = $true
    }    
  }
  try{
    if($ApplicationJSONUpdated){
      if($script:AppFactoryLogging){
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating App.JSON file $($ApplicationJSONPath)" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationJSON | ConvertTo-Json | Out-File -FilePath $ApplicationJSONPath
    }
    if($ApplicationConfigUpdated){
      if($script:AppFactoryLogging){
        Write-PSFMessage -Message "[$($app.IntuneAppName)] Updating Config.JSON file $($ApplicationConfigPath)" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      $ApplicationConfig | ConvertTo-Json | Out-File -FilePath $ApplicationConfigPath
    }
  }
  catch{
    Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Application", "$($Name)" -Target "Application Factory Service"
    throw $_    
  }  
}
#EndRegion '.\Public\Set-AppFactoryAppConfiguration.ps1' 259
#Region '.\Public\Set-AppFactoryAppDetectionRule.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to update the detection rule for an application
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER Type
  The type of detection that we will be doing
  .PARAMETER ProductVersionOperator
  How we should be comparing the product version
  .PARAMETER DetectionMethod
  How we should be checking for key
  .PARAMETER KeyPath
  The last part of the uninstall registry key path if not replaced automatically during process
  .PARAMETER ValueName
  The key that we will be looking for
  .PARAMETER DetectionType
  Should the key exist or not exist
  .PARAMETER Check32BitOn64System
  If this should be checked 32 bit on a 64 bit OS
  .PARAMETER EnforceSignatureCheck
  If the script should be signed to run
  .PARAMETER RunAs32Bit
  If this should be checked 32 bit on a 64 bit OS
  .PARAMETER Operator
  How we should be comparing the product version
  .PARAMETER ScriptFile
  The name of the script file used for detection
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
 
  .EXAMPLE
 
  Create a detection rule that is checking for a specific version based on the MSI product code
  $params = @{
    GUID = "### GUID ###"
    Type = "Registry"
    ProductVersionOperator = "greaterThanOrEqual"
    DetectionMethod = "VersionComparison"
    LogLevel = "Output"
  }
  Set-AppFactoryAppDetectionRule @params
 
  Create a detection rule that is looking for the existance of a specific registry value
  $params = @{
    GUID = "### GUID ###"
    Type = "Registry"
    DetectionMethod = "Existence"
    KeyPath = "### KEY ###"
    ValueName = "### VALUE ###"
    DetectionType = "exists"
    LogLevel = "Output"
  }
  Set-AppFactoryAppDetectionRule @params
 
  Create a detection rule that is based on the existance of a MSI
  $params = @{
    GUID = "### GUID ###"
    Type = "MSI"
    ProductVersionOperator = "notConfigured"
    DetectionMethod = "Existence"
    LogLevel = "Output"
  }
  Set-AppFactoryAppDetectionRule @params
#>

function Set-AppFactoryAppDetectionRule{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter(Mandatory = $true)][ValidateSet("MSI","Registry","Script")][string]$Type,
    [Parameter()][ValidateSet("notConfigured","equal","notEqual","greaterThanOrEqual","greaterThan","lessThanOrEqual","lessThan")][string]$ProductVersionOperator = "notConfigured",
    [Parameter()][ValidateSet("Existence","VersionComparison")][string]$DetectionMethod,
    [Parameter()][ValidateNotNullOrEmpty()][string]$KeyPath = "###PRODUCTCODE###",
    [Parameter()][ValidateNotNullOrEmpty()][string]$ValueName = "DisplayVersion",
    [Parameter()][ValidateSet("exists","notExists")][string]$DetectionType,
    [Parameter()][switch]$Check32BitOn64System = $false,
    [Parameter()][Switch]$EnforceSignatureCheck = $false,
    [Parameter()][Switch]$RunAs32Bit = $false,
    [Parameter()][ValidateSet("notConfigured","equal","notEqual","greaterThanOrEqual","greaterThan","lessThanOrEqual","lessThan")][string]$Operator = "greaterThanOrEqual",
    [Parameter()][ValidateNotNullOrEmpty()][string]$ScriptFile = "detection.ps1",
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  # Check if the application that we are trying to update actually exist
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel
  if (-not ($application)) {
    throw "Application with GUID $($GUID) does not exist."
  }
  # Create a object with the details for the detection type
  $detectionRule = [PSCustomObject]@{
    "Type"                    = $Type
  }
  # Depending on the type, set the appropriate details
  switch($Type){
    "MSI"{
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "ProductCode" -Value "<replaced_by_pipeline>"
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "ProductVersionOperator" -Value $ProductVersionOperator
      if($ProductVersionOperator -ne "notConfigured"){
        $detectionRule | Add-Member -MemberType "NoteProperty" -Name "ProductVersion" -Value "<replaced_by_pipeline>"
      }
    }
    "Registry"{
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "DetectionMethod" -Value $DetectionMethod
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "KeyPath" -Value "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$($KeyPath)"
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "ValueName" -Value $ValueName
      if($DetectionType){
        $detectionRule | Add-Member -MemberType "NoteProperty" -Name "DetectionType" -Value $DetectionType
      }
      if($Operator){
        $detectionRule | Add-Member -MemberType "NoteProperty" -Name "Operator" -Value $Operator
        $detectionRule | Add-Member -MemberType "NoteProperty" -Name "Value" -Value "<replaced_by_pipeline>"
      }
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "Check32BitOn64System" -Value "$($Check32BitOn64System.IsPresent)"
    }
    "Script"{
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "ScriptFile" -Value $ScriptFile
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "EnforceSignatureCheck" -Value "$($EnforceSignatureCheck.IsPresent)"
      $detectionRule | Add-Member -MemberType "NoteProperty" -Name "RunAs32Bit" -Value "$($RunAs32Bit.IsPresent)"
    }
  }
  # Write out the details to the configuration
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel
  $applicationJSONfile = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName,"App.json"
  $applicationJSON = Get-Content -Path $applicationJSONfile | ConvertFrom-Json -Depth 5
  $applicationJSON.DetectionRule = ,$detectionRule
  try{
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Updating - Detection Rule" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
    }
    $applicationJSON | ConvertTo-Json -Depth 5 | Out-File -FilePath $applicationJSONfile
  }
  catch{
    throw $_
  }
}
#EndRegion '.\Public\Set-AppFactoryAppDetectionRule.ps1' 134
#Region '.\Public\Set-AppFactoryAppInstall.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to update the install commands in deploy-application.ps1
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER Type
  The type of installation that we will be using for this application
  .PARAMETER Script
  If custom script, the contents for the script file
  .PARAMETER Parameters
  Any parameters used by the command line
  .PARAMETER exe
  The name of the exe that will be used, if not substituted
  .PARAMETER msi
  The name of the msi that will be used, if not substituted
  .PARAMETER mst
  The mst file if required
  .PARAMETER beginstopProcess
  Process to stop at the beginning of an install
  .PARAMETER afterstopProcess
  Process to stop after the install
  .PARAMETER ignoreExit
  If there are exit codes we should ignore
  .PARAMETER WIM
  If this is run from a WIM file
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Set-AppFactoryAppInstall {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter(Mandatory = $true)][ValidateSet("Custom", "PowerShell", "ECNO", "EXE", "MSI")][string]$Type,
    [Parameter()][ValidateNotNullOrEmpty()][string]$script,
    [Parameter()][string]$Parameters,
    [Parameter()][ValidateNotNullOrEmpty()][string]$installer = "###SETUPFILENAME###",
    [Parameter()][ValidateNotNullOrEmpty()][string]$mst,
    [Parameter()][string]$beginstopProcess,
    [Parameter()][string]$afterstopProcess,
    [Parameter()][string]$ignoreExit,
    [Parameter()][switch]$WIM,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Get application that we will be working with
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel
  if (-not ($application)) {
    throw "Application with GUID $($GUID) does not exist."   
  }
  # Create a collection for the lines that we will add to the deploy-application.ps1
  $ApplicationInstallLines = @{
    "PreInstall"  = [System.Collections.Generic.List[String[]]]@()
    "Install"     = [System.Collections.Generic.List[String[]]]@()
    "PostInstall" = [System.Collections.Generic.List[String[]]]@()
  }
  # Depending on the type of install, will depend on the lines that we need to add
  $installType = $Type.ToUpper()
  $installerPath = "`$dirFiles"
  if ($WIM) {
    $installerPath = "`$MountPath"
  }
  $ApplicationInstallLines.Install.Add(("###INSTALLTYPE:$($installType)###", 2)) | Out-Null
  $ApplicationInstallLines.Install.Add(("###INSTALLCMDSTART###", 2)) | Out-Null 
  if ($WIM) {
    foreach ($line in $(Add-AppFactoryAppWIM -section "start" -application $application)) {
      $ApplicationInstallLines.Install.Add($line)
    }
  }
  $commonParams = @{
    ApplicationInstallLines = $ApplicationInstallLines
    directory               = $installerPath
    installer               = $installer
    Parameters              = $Parameters
    LogLevel                = $LogLevel
  }  
  switch ($Type) {
    "MSI" {
      foreach ($line in $(Add-AppFactoryApplicationScriptMSI @commonParams -mst $mst)) {
        $ApplicationInstallLines.Install.Add($line)
      }       
    }
    "EXE" {
      foreach ($line in $(Add-AppFactoryApplicationScriptEXE @commonParams -ignoreExit $ignoreExit)) {
        $ApplicationInstallLines.Install.Add($line)
      }      
    }
    "Custom" {
      $ApplicationInstallLines.Install.Add(($script, 2)) | Out-Null
    }
    "PowerShell" {
      $ApplicationInstallLines.Install.Add(("Execute-Process -Path `"powershell.exe`" -Parameters `"-ExecutionPolicy Bypass -File `"`"`$($($installerPath))\$($script)`"`"`"", 2)) | Out-Null
    }
    "ECNO" {
      $ApplicationInstallLines.Install.Add(("Push-Location `$installerPath", 2)) | Out-Null
      $ApplicationInstallLines.Install.Add(("Start-Process -FilePath powershell.exe -ArgumentList `"-ExecutionPolicy Bypass -File _action.ps1 install`" -NoNewWindow -Wait", 2)) | Out-Null
      $ApplicationInstallLines.Install.Add(("Pop-Location", 2)) | Out-Null
    }
  }
  if ($WIM) {
    foreach ($line in $(Add-AppFactoryAppWIM -section "end" -application $application)) {
      $ApplicationInstallLines.Install.Add($line)
    }
  }
  $ApplicationInstallLines.Install.Add(("###INSTALLCMDEND###", 2)) | Out-Null
  # Any pre-install processes that need to happen
  $ApplicationInstallLines.PreInstall.Add(("###INSTALL:PRESTART###", 2)) | Out-Null
  if ($beginstopProcess) {
    $params = @{
      interactive     = $true
      blockingProcess = $beginstopProcess
      deferCount      = 3
      LogLevel        = $LogLevel
    }
    foreach ($line in $(Add-AppFactoryApplicationBlockingProcess @params)) {
      $ApplicationInstallLines.PreInstall.Add($line)
    }
  }
  $ApplicationInstallLines.PreInstall.Add(("###INSTALL:PREEND###", 2)) | Out-Null
  # Any post install process that need to happen
  $ApplicationInstallLines.PostInstall.Add(("###INSTALL:POSTSTART###", 2)) | Out-Null
  if ($afterstopProcess) {
    $params = @{
      interactive     = $false
      blockingProcess = $afterstopProcess
      LogLevel        = $LogLevel
    }
    foreach ($line in $(Add-AppFactoryApplicationBlockingProcess @params)) {
      $ApplicationInstallLines.PostInstall.Add($line)
    }
  }
  $ApplicationInstallLines.PostInstall.Add(("###INSTALL:POSTEND###", 2)) | Out-Null  
  # Get and read the current deployment file
  $ApplicationDeploymentFilePath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName, "Deploy-Application.ps1"  

  # Update the deployment file with the pre-install steps
  $common = @{
    Script   = $ApplicationDeploymentFilePath
    LogLevel = $LogLevel
    section  = "install"
  }
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Processing Pre Install Script Lines" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }  
  Set-AppFactoryDeploymentScriptLines @common -ScriptLines $ApplicationInstallLines.PreInstall -LogLevel $LogLevel
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Processing Post Install Script Lines" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }  
  Set-AppFactoryDeploymentScriptLines @common -ScriptLines $ApplicationInstallLines.PostInstall -LogLevel $LogLevel
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Processing Install Script Lines" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }    
  Set-AppFactoryDeploymentScriptLines @common -main -ScriptLines $ApplicationInstallLines.Install -LogLevel $LogLevel
}
#EndRegion '.\Public\Set-AppFactoryAppInstall.ps1' 153
#Region '.\Public\Set-AppFactoryAppOrgConfig.ps1' 0
function Set-AppFactoryAppOrgConfig{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$orgGUID,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$appGUID,
    [Parameter()][bool]$AddToIntune = $false,
    [Parameter()][string[]]$AvailableAssignments = @(),
    [Parameter()][string[]]$AvailableExceptions = @(),
    [Parameter()][string[]]$RequiredAssignments = @(),
    [Parameter()][string[]]$RequiredExceptions = @(),
    [Parameter()][string[]]$UninstallAssignments = @(),
    [Parameter()][string[]]$UninstallExceptions = @(),
    [Parameter()][bool]$UnassignPrevious = $true,
    [Parameter()][bool]$CopyPrevious = $true,
    [Parameter()][int]$KeepPrevious = 0,
    [Parameter()][bool]$foreground = $false,
    [Parameter()][hashtable]$filters = @{},
    [Parameter()][string[]]$espprofiles = @(),
    [Parameter()][bool]$InteractiveInstall = $false,
    [Parameter()][bool]$InteractiveUninstall = $false
  )
  $configFile = Join-Path -Path $script:AppFactorySourceDir -ChildPath "DSBConfigurations" -AdditionalChildPath $orgGUID,"$($appGUID).json"
  $obj = [PSCustomObject]@{
    "AddToIntune"           = $AddToIntune
    "AvailableAssignments"  = $AvailableAssignments
    "AvailableExceptions"   = $AvailableExceptions
    "RequiredAssignments"   = $RequiredAssignments
    "RequiredExceptions"    = $RequiredExceptions
    "UninstallAssignments"  = $UninstallAssignments
    "UninstallExceptions"   = $UninstallExceptions
    "UnassignPrevious"      = $UnassignPrevious
    "CopyPrevious"          = $CopyPrevious
    "KeepPrevious"          = $KeepPrevious
    "foreground"            = $foreground
    "filters"               = $filters
    "espprofiles"           = $espprofiles 
    "InteractiveInstall"    = $InteractiveInstall
    "InteractiveUninstall"  = $InteractiveUninstall       
  }
  $obj | ConvertTo-JSON -Depth 5 | Out-File $configFile
}
#EndRegion '.\Public\Set-AppFactoryAppOrgConfig.ps1' 42
#Region '.\Public\Set-AppFactoryAppUninstall.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to update the uninstall commands in deploy-application.ps1
  .PARAMETER GUID
  The unique identifier for the application that we want to work with
  .PARAMETER Type
  The type of installation that we will be using for this application
  .PARAMETER Script
  If custom script, the contents for the script file
  .PARAMETER Parameters
  Any parameters used by the command line
  .PARAMETER exe
  The name of the exe that will be used, if not substituted
  .PARAMETER MSIGUID
  The GUID for the MSI that is to be removed
  .PARAMETER stopProcess
  Process to stop at the beginning of an uninstall
  .PARAMETER dirFiles
  If should be run from the dir files folder
  .PARAMETER ignoreExit
  If there are exit codes we should ignore
  .PARAMETER WIM
  If this is run from a WIM file
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Set-AppFactoryAppUninstall {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter(Mandatory = $true)][ValidateSet("None", "MSI", "EXE", "Name", "GUID", "ECNO", "Custom")][string]$Type,
    [Parameter()][ValidateNotNullOrEmpty()][string]$name,
    [Parameter()][ValidateNotNullOrEmpty()][string]$script,
    [Parameter()][ValidateNotNullOrEmpty()][string]$installer = "###SETUPFILENAME###",
    [Parameter()][string]$Parameters, 
    [Parameter()][string]$beginstopProcess,
    [Parameter()][string]$afterstopProcess,
    [Parameter()][switch]$WIM,
    [Parameter()][switch]$dirFiles,
    [Parameter()][string]$ignoreExit,
    [Parameter()][ValidateNotNullOrEmpty()][string]$MSIGUID,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose" 
  )
  $application = Get-AppFactoryApp -GUID $GUID -LogLevel $LogLevel
  if (-not ($application)) {
    throw "Application with GUID $($GUID) does not exist."   
  }
  # Create a collection for the lines that we will add to the deploy-application.ps1
  $ApplicationUninstallLines = @{
    "PreUninstall"  = [System.Collections.Generic.List[String[]]]@()
    "Uninstall"     = [System.Collections.Generic.List[String[]]]@()
    "PostUninstall" = [System.Collections.Generic.List[String[]]]@()
  }
  # Depending on the type of install, will depend on the lines that we need to add
  $uninstallType = $Type.ToUpper()
  $installerPath = ""
  if ($dirFiles.IsPresent) {
    $installerPath = "`$dirFiles"
  }
  if ($WIM) {
    $installerPath = "`$MountPath"
  }
  $ApplicationUninstallLines.Uninstall.Add(("###UNINSTALLTYPE:$($uninstallType)###", 2)) | Out-Null
  $ApplicationUninstallLines.Uninstall.Add(("###UNINSTALLCMDSTART###", 2)) | Out-Null
  if ($WIM) {
    foreach ($line in $(Add-AppFactoryAppWIM -section "start" -application $application)) {
      $ApplicationUninstallLines.Uninstall.Add($line)
    }
  }
  $commonParams = @{
    ApplicationInstallLines = $ApplicationUninstallLines
    directory               = $installerPath
    installer               = $installer
    Parameters              = $Parameters
    LogLevel                = $LogLevel
  }    
  switch ($Type) {
    "EXE" {
      foreach ($line in $(Add-AppFactoryApplicationScriptEXE @commonParams -ignoreExit $ignoreExit)) {
        $ApplicationUninstallLines.Uninstall.Add($line)
      }     
    }
    "MSI" {
      foreach ($line in $(Add-AppFactoryApplicationScriptMSI @commonParams -msiAction "Uninstall")) {
        $ApplicationUninstallLines.Uninstall.Add($line)
      }       
    }
    "Name"{
      $ApplicationUninstallLines.Uninstall.Add(("Remove-MSIApplications -Name `"$($Name)`"", 2)) | Out-Null
    }
    "GUID"{
      $ApplicationUninstallLines.Uninstall.Add(("Execute-MSI -Action `"Uninstall`" -Path `"$($MSIGUID)`"", 2)) | Out-Null
    }
    "ECNO"{
      $ApplicationUninstallLines.Uninstall.Add(("Push-Location `$installerPath", 2)) | Out-Null
      $ApplicationUninstallLines.Uninstall.Add(("Start-Process -FilePath powershell.exe -ArgumentList `"-ExecutionPolicy Bypass -File _action.ps1 remove`" -NoNewWindow -Wait", 2)) | Out-Null
      $ApplicationUninstallLines.Uninstall.Add(("Pop-Location", 2)) | Out-Null
    }
    "Custom"{
      $ApplicationUninstallLines.Uninstall.Add(($script, 2)) | Out-Null
    }
  }

  if ($WIM) {
    foreach ($line in $(Add-AppFactoryAppWIM -section "end" -application $application)) {
      $ApplicationUninstallLines.Uninstall.Add($line)
    }
  }
  $ApplicationUninstallLines.Uninstall.Add(("###UNINSTALLCMDEND###", 2)) | Out-Null
  # Any pre-install processes that need to happen
  $ApplicationUninstallLines.PreUninstall.Add(("###UNINSTALL:PRESTART###", 2)) | Out-Null
  if ($beginstopProcess) {
    $params = @{
      interactive     = $true
      blockingProcess = $beginstopProcess
      deferCount      = 3
      LogLevel        = $LogLevel
    }
    foreach ($line in $(Add-AppFactoryApplicationBlockingProcess @params)) {
      $ApplicationUninstallLines.PreUninstall.Add($line)
    }
  }
  $ApplicationUninstallLines.PreUninstall.Add(("###UNINSTALL:PREEND###", 2)) | Out-Null
  # Any post install process that need to happen
  $ApplicationUninstallLines.PostUninstall.Add(("###UNINSTALL:POSTSTART###", 2)) | Out-Null
  if ($afterstopProcess) {
    $params = @{
      interactive     = $false
      blockingProcess = $afterstopProcess
      LogLevel        = $LogLevel
    }
    foreach ($line in $(Add-AppFactoryApplicationBlockingProcess @params)) {
      $ApplicationUninstallLines.PostUninstall.Add($line)
    }
  }
  $ApplicationUninstallLines.PostUninstall.Add(("###UNINSTALL:POSTEND###", 2)) | Out-Null 
  # Get and read the current deployment file
  $ApplicationDeploymentFilePath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName, "Deploy-Application.ps1"  

  # Update the deployment file with the pre-install steps
  $common = @{
    Script   = $ApplicationDeploymentFilePath
    LogLevel = $LogLevel
    section  = "Uninstall"
  }
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Processing Pre Install Script Lines" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }    
  Set-AppFactoryDeploymentScriptLines @common -ScriptLines $ApplicationUninstallLines.PreUninstall -LogLevel $LogLevel
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Processing Post Install Script Lines" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }  
  Set-AppFactoryDeploymentScriptLines @common -ScriptLines $ApplicationUninstallLines.PostUninstall -LogLevel $LogLevel 
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Processing Install Script Lines" -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
  }    
  Set-AppFactoryDeploymentScriptLines @common -main -ScriptLines $ApplicationUninstallLines.Uninstall -LogLevel $LogLevel
}
#EndRegion '.\Public\Set-AppFactoryAppUninstall.ps1' 159
#Region '.\Public\Set-AppFactoryOrganization.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to update the details of an organization
  .PARAMETER GUID
  The unique identifier for the organization that we want to work with
    .PARAMETER name
  The name of the organization
  .PARAMETER contactList
  The list of contacts for the organization
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
 
  .EXAMPLE
 
  Update an organizations name
    Set-AppFactoryOrganization -GUID "### GUID ###" -Name "### Organization Name ###"
 
  Update an organization cotacts
    Set-AppFactoryOrganization -GUID "### GUID ###" -contactList "### Contact 1 ###", "### Contact 2 ###"
#>

function Set-AppFactoryOrganization {
  [CmdletBinding()]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$GUID,
    [Parameter()][ValidateNotNullOrEmpty()][string]$Name,
    [Parameter()][string[]]$contactList,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"  
  )
  # Get Org Configuration Files
  $organization = Get-AppFactoryOrganization -GUID $GUID -LogLevel $LogLevel
  if ($null -eq $organization) {
    Write-PSFMessage -Message "Error Encountered: Organization not found." -Level "Error" -Tag "Organizations" -Target "Application Factory Service"
    throw "Organization not found." 
  }
  # Flag to know if we actually updated anything
  $changed = $false
  # If Contact List is Set to Be Updated
  if($contactList.Count -gt 0){
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($organization.Name)] Updating Contact List - <c='green'>$($contactList)</c>" -Level $LogLevel -Tag "Organizations","ContactList","$($organization.Name)" -Target "Application Factory Service"
    }
    if($contactList){
      $organization.Contacts = $contactList
    }
    else{
      $organization.Contacts = @()
    }
    $changed = $true
  }
  # If Name is set to be updated
  if($Name){
    if($script:AppFactoryLogging){
      Write-PSFMessage -Message "[$($organization.Name)] Updating Name - <c='green'>$($Name)</c>" -Level $LogLevel -Tag "Organizations","Name","$($organization.Name)" -Target "Application Factory Service"
    }
    $organization.Name = $Name
    $changed = $true
  }
  # If something has been changed, lets do some updates
  if($changed){
    # Get Current Path of Organization File
    $fileName = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Organizations" -AdditionalChildPath $organization.FileName
    # Remove the filename property since we will be writing out the configuration
    $organization.PsObject.properties.Remove("fileName")
    try{
      if($script:AppFactoryLogging){
        Write-PSFMessage -Message "[$($organization.Name)] Saving configuration file" -Level $LogLevel -Tag "Organizations","$($organization.Name)" -Target "Application Factory Service"
      }
      $organization | ConvertTo-Json -Depth 3 | Out-File $fileName
      if($Name){
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($organization.Name)] Renmaing configuration file" -Level $LogLevel -Tag "Organizations","$($organization.Name)" -Target "Application Factory Service"
        }
        Rename-Item -Path $fileName -NewName "$($Name).json" -Force
      }
    }
    catch{
      Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Organizations" -Target "Application Factory Service"
      throw $_   
    }
  }  
}
#EndRegion '.\Public\Set-AppFactoryOrganization.ps1' 82
#Region '.\Public\Start-AppFactory.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to set all the required variables and settings for the process
  .PARAMETER Path
  Where the application configuration files are stored
  .PARAMETER configuration
  The name of the configuration file if not the default
  .PARAMETER PSU
  If we are running this and using variables stored in PSU
  .PARAMETER LocalModule
  If we should load the module from a local source vs installed module
  .PARAMETER EnableLogging
  If logging should be enabled with PSFramework
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
  .EXAMPLE
 
  Start Application Factory with the default configuration file and no logging
    Start-AppFactory -Path "### PATH TO PROCESS FILES ###"
 
  Start Application Factory with a different configuration file and logging enabled
    Start-AppFactory -Path "### PATH TO PROCESS FILES ###" -configuration "### CONFIGURATION JSON FILE NAME ###" -EnableLogging
 
  Start Application Factory as if running from Powershell Universal and using platform variables
    Start-AppFactory -PSU
 
  Start Application Factory with a local module loaded for testing
    Start-AppFactory -Path "### PATH TO PROCESS FILES ###" -LocalModule "### PATH TO LOCAL MODULE ###"
#>

function Start-AppFactory {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true, ParameterSetName = 'console')][ValidateNotNullOrEmpty()][string]$Path,
    [Parameter(Mandatory = $true, ParameterSetName = 'PSU')][switch]$PSU,
    [Parameter()][ValidateNotNullOrEmpty()][string]$configuration = "Configuration.json",
    [Parameter()][string]$LocalModule = $null,
    [Parameter()][switch]$EnableLogging,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Set if we should be logging with the PSFramework Module
  $script:AppFactoryLogging = $EnableLogging.IsPresent
  # Should we be loading a local module to use for the processes (mainly used for testing multithreading)
  $script:LocalModulePath = $LocalModule
  # Determine what the Source Directory we aer using is
  if ($PSCmdlet.ParameterSetName -eq "console") { $script:AppFactorySourceDir = $Path } 
  # Where are the supporting files stored
  $script:AppFactorySupportFiles = Join-Path -Path $PSScriptRoot -ChildPath "SupportFiles"
  $script:AppFactorySupportTemplateFolder = Join-Path -Path $PSScriptRoot -ChildPath "Templates"
  $script:AppFactoryLocalSupportFiles = Join-Path -Path $Path -ChildPath "SupportFiles"
  $script:AppFactoryWorkspace = Join-Path -Path $Path -ChildPath "Workspace"
  # Setup Logging Configuration
  if ($script:AppFactoryLogging) {
    $AppFactoryLogDir = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Logs"
    # Name for the log file to be used
    $logFile = "$($AppFactoryLogDir)\AppFactory-%Date%.csv"   
    $paramSetPSFLoggingProvider = @{
      Name         = "logfile"
      InstanceName = "AppFactory"
      FilePath     = $logFile
      Enabled      = $true
      Wait         = $true
    }
    Set-PSFLoggingProvider @paramSetPSFLoggingProvider 
    Write-PSFMessage -Message "Logging Configured" -Level $LogLevel -Tag "Setup" -Target "Application Factory Service"
    Write-PSFMessage -Message "Log File: <c='green'>$($logFile)</c>" -Level $LogLevel -Tag "Setup" -Target "Application Factory Service"
    Write-PSFMessage -Message "Reading Configuration File" -Level $LogLevel -Tag "Setup" -Target "Application Factory Service"     
  }
    
  # If running this in the console, configure the variables that normally would be in PowerShell Universal
  if ($PSCmdlet.ParameterSetName -eq "console") {
    try {
      # Where is the configuration folder
      $script:AppFactoryConfigDir = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Configurations"
      # What is the path to the configuration file
      $AFConfigFile = Join-Path $script:AppFactoryconfigDir -ChildPath $configuration
      # Read the configuration file into an object
      $configDetails = Get-Content -Path $AFConfigFile -ErrorAction Stop | ConvertFrom-JSON
      $script:AppFactoryInstallersContainer = $configDetails.storage.installers.name
      $script:AppFactoryInstallersSecret = Get-Secret -Vault $configDetails.keyVault -Name $configDetails.storage.installers.secret
      $script:AppFactoryDeploymentsContainer = $configDetails.storage.deployments.name
      $script:AppFactoryDeploymentsSecret = Get-Secret -Vault $configDetails.keyVault -Name $configDetails.storage.deployments.secret
      $script:AppFactoryPublicFolder = $configDetails.publicContainer
      $script:AppFactorySharepointTenant = $configDetails.sharepoint.tenant
      $script:AppFactorySharepointClientID = $configDetails.sharepoint.clientId
      $script:AppFactorySharrepointURL = $configDetails.sharepoint.url
      $script:AppFactorySharepointCertificate = $configDetails.sharepoint.certificateFile
      $script:AppFactorySharepointCertificateSecret = Get-Secret -Vault $configDetails.keyVault -Name $configDetails.sharepoint.certificateSecret
      $script:AppFactorySharepointVersionField = $configDetails.sharepoint.versionField
      $script:AppFactorySharepointDocumentLibrary = $configDetails.sharepoint.documentLibrary  
    }
    catch {
      Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Setup" -Target "Application Factory Service"
      throw $_
    }        
  }
  else {
    try {
      # Only variables needed for PSU based version are converting the secrets to readable format
      $script:AppFactoryInstallersSecret = ConvertTo-SecureString $secret:AppFactoryInstallersSecret -AsPlainText -Force
      $script:AppFactoryDeploymentsSecret = ConvertTo-SecureString $secret:AppFactoryDeploymentsSecret -AsPlainText -Force
      $script:AppFactorySharepointCertificateSecret = $secret:AppFactorySharepointCertificateSecret        
    }
    catch {
      Write-PSFMessage -Message "Error Encountered: $($_)" -Level "Error" -Tag "Setup" -Target "Application Factory Service"
      throw $_        
    }      
  }
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "Loaded variables for the Application Factory Service" -Level $LogLevel -Tag "Setup" -Target "Application Factory Service"
  }    
}
#EndRegion '.\Public\Start-AppFactory.ps1' 112
#Region '.\Public\Start-AppFactoryProcess.ps1' 0
function Start-AppFactoryProcess {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true, ParameterSetName = 'console')][ValidateNotNullOrEmpty()][string]$Path,
    [Parameter()][string]$LocalModule = $null,
    [Parameter()][ValidateNotNullOrEmpty()][string]$configuration = "Configuration.json",
    [Parameter()][switch]$EnableLogging,
    [Parameter()][switch]$multithread,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  $processparams = @{
    "Path"          = $Path
    "configuration" = $configuration
    "LocalModule"   = $LocalModule
    "EnableLogging" = $EnableLogging
    "LogLevel"      = $LogLevel
  }
  $multithreadParam = @{
    "multithread" = $multithread
  }
  Start-AppFactory @processparams
  try {
    Remove-AppFactoryWorkspaceFiles
    # Get Applications
    $applicationList = Get-AppFactoryApp -LogLevel $LogLevel
    Write-Host "There are $($applicationList.count) applications configured in AppFactory" -ForegroundColor Green
    Write-Host "Getting the latest version number for each application in the process" -ForegroundColor Green
    $appVersions = Test-AppFactoryAppNewVersion -LogLevel $LogLevel -applicationList $applicationList @multithreadParam
    $applicationList = Compare-AppFactoryAppVersions -LogLevel $LogLevel -applicationList $applicationList -appVersions $appVersions
    Write-Host "There are $($applicationList.count) apps that require an update." -ForegroundColor Green
    if ($applicationList.count -eq 0) {
      return
    }
    $applicationList = Test-AppFactoryFiles -LogLevel $LogLevel -applicationList $applicationList
    Write-Host "There are $($applicationList.count) apps that are ready to download files." -ForegroundColor Green
    $applicationList = Get-AppFactoryInstaller -LogLevel $LogLevel -applicationList $applicationList @multithreadParam
    Write-Host "Successfully downloaded files for $($applicationList.count) applications." -ForegroundColor Green
    $applicationList = Add-AppFactoryPackageFolder -LogLevel $LogLevel -applicationList $applicationList @multithreadParam
    Write-Host "Successfully created publishing files for $($applicationList.count) applications." -ForegroundColor Green
    $applicationList = New-AppFactoryIntuneFile -LogLevel $LogLevel -applicationList $applicationList @multithreadParam
    Write-Host "Successfully created intune application files for $($applicationList.count) applications." -ForegroundColor Green
    $applicationList = Publish-AppFactoryApp -LogLevel $LogLevel -applicationList $applicationList
    foreach($application in $applicationList){
      Write-Host "Application Published: $($application.IntuneAppName)" -ForegroundColor Green
    }  
  }
  catch {
    throw $_
  }
}
#EndRegion '.\Public\Start-AppFactoryProcess.ps1' 51
#Region '.\Public\Test-AppFactoryAppNewVersion.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to get all the current versions of the applications in the process
  .PARAMETER applicationList
  The collection of the applications that we will be acting on
  .PARAMETER force
  If we should continue either way
  .PARAMETER multithread
  Show we proceed single threaded or multithreaded
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Test-AppFactoryAppNewVersion {
  [CmdletBinding()]
  [OutputType([System.Collections.Hashtable])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter()][ValidateNotNullOrEmpty()][switch]$force,
    [Parameter()][ValidateNotNullOrEmpty()][switch]$multithread,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  $applications = [hashtable]::Synchronized(@{})
  if ($script:AppFactoryLogging) {
    Write-PSFMessage -Message "Connecting to Azure Storage"  -Level $LogLevel -Tag "Application", "AzureStorage" -Target "Application Factory Service"
  }
  $appStorageContext = Connect-AppFactoryAzureStorage -storageContainer $script:AppFactoryInstallersContainer -storageSecret $script:AppFactoryInstallersSecret -LogLevel $LogLevel
  $psadtStorageContext = Connect-AppFactoryAzureStorage -storageContainer $script:AppFactoryDeploymentsContainer -storageSecret $script:AppFactoryDeploymentsSecret -LogLevel $LogLevel
  $pfxPath = Join-Path -Path $script:AppFactoryLocalSupportFiles -ChildPath $script:AppFactorySharepointCertificate
  $sharepointConfig = @{
    "url"                 = $script:AppFactorySharrepointURL
    "CertificatePath"     = $pfxPath
    "CertificatePassword" = $script:AppFactorySharepointCertificateSecret.Password
    "ClientId"            = $script:AppFactorySharepointClientID
    "Tenant"              = $script:AppFactorySharepointTenant
  }
  if (-not $multithread.IsPresent) {
    try { Connect-PnPOnline @sharepointConfig }
    catch {}
    foreach ($application in $applicationList) {
      $AppItem = $null
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($application.IntuneAppName)] Checking for new version in <c='green'>$($application.AppSource)</c>"  -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      try {
        switch ($application.AppSource) {
          "Winget" { $AppItem = Get-AppFactoryWinGetAppItem -application $application -LogLevel $LogLevel }
          "Evergreen" { $AppItem = Get-AppFactoryEvergreenAppItem -application $application -LogLevel $LogLevel }
          "StorageAccount" { $AppItem = Get-AppFactoryAzureStorageAppItem -application $application -storageContext $appStorageContext -workingfolder $script:AppFactoryWorkspace  -LogLevel $LogLevel }
          "Sharepoint" { $AppItem = Get-AppFactorySharepointAppItem -application $application -documentLibrary $script:AppFactorySharepointDocumentLibrary -LogLevel $LogLevel }
          "ECNO" { $AppItem = Get-AppFactoryECNOAppItem -application $application -documentLibrary $script:AppFactorySharepointDocumentLibrary -LogLevel $LogLevel }
          "PSADT" { $AppItem = Get-AppFactoryAzurePSADTAppItem -application $application -storageContext $psadtStorageContext  -LogLevel $LogLevel }
        }
        $applications.Add($application.GUID, $AppItem)
      }
      catch {
        $AppItem = [PSCustomObject]@{
          "Version"  = $version
          "URI"      = $null
          "BlobName" = $null
          "Status"   = "Error $($_)"
        }
        $applications.Add($application.GUID, $AppItem)
        if ($script:AppFactoryLogging) {
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Error getting version $($_)" -Level "Error" -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
        }
      }
    }
  }
  else {
    if ($script:LocalModulePath) {
      $modulePath = $script:LocalModulePath
    }
    else {
      $modulePath = (Get-module ApplicationFactoryService).path
    }
    $applicationList | Foreach-Object -Parallel {
      Import-Module -Name $using:modulePath -Force
      $application = $_
      $applications = $using:applications
      $appStorageContext = $using:appStorageContext
      $psadtStorageContext = $using:psadtStorageContext
      $script:AppFactoryLogging = $using:script:AppFactoryLogging
      $script:AppFactoryWorkspace = $using:script:AppFactoryWorkspace
      $script:AppFactorySharepointDocumentLibrary = $using:script:AppFactorySharepointDocumentLibrary
      $AppItem = $null
      $LogLevel = $using:LogLevel
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($application.IntuneAppName)] Checking for new version in <c='green'>$($application.AppSource)</c>"  -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "AppFactory"
      }
      if ($application.AppSource -in @("Sharepoint", "ECNO")) {
        try { Connect-PnPOnline @using:sharepointConfig }
        catch {}        
      }
      try {
        switch ($application.AppSource) {
          "Winget" { $AppItem = Get-AppFactoryWinGetAppItem -application $application -LogLevel $LogLevel }
          "Evergreen" { $AppItem = Get-AppFactoryEvergreenAppItem -application $application -LogLevel $LogLevel }
          "StorageAccount" { $AppItem = Get-AppFactoryAzureStorageAppItem -application $application -storageContext $appStorageContext -workingfolder $script:AppFactoryWorkspace  -LogLevel $LogLevel }
          "Sharepoint" { 
            $AppItem = Get-AppFactorySharepointAppItem -application $application -documentLibrary $script:AppFactorySharepointDocumentLibrary -LogLevel $LogLevel
          }
          "ECNO" {
            $AppItem = Get-AppFactoryECNOAppItem -application $application -documentLibrary $script:AppFactorySharepointDocumentLibrary -LogLevel $LogLevel
          }
          "PSADT" { $AppItem = Get-AppFactoryAzurePSADTAppItem -application $application -storageContext $psadtStorageContext  -LogLevel $LogLevel }
        }
        $applications.Add($application.GUID, $AppItem)
      }
      catch {
        $AppItem = [PSCustomObject]@{
          "Version"  = $version
          "URI"      = $null
          "BlobName" = $null
          "Status"   = "Error $($_)"
        }
        $applications.Add($application.GUID, $AppItem)
        if ($script:AppFactoryLogging) {
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Error getting version $($_)" -Level "Error" -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
        }
      }      
    }
  }
  return $applications
}
#EndRegion '.\Public\Test-AppFactoryAppNewVersion.ps1' 125
#Region '.\Public\Test-AppFactoryFiles.ps1' 0
<#
  .DESCRIPTION
  This cmdlet is designed to confirm everything in the config files needed is present
  .PARAMETER applicationList
  The collection of the applications that we will be acting on
  .PARAMETER LogLevel
  If logging is enabled, what level of logging do we want, default is verbose.
#>

function Test-AppFactoryFiles {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$applicationList,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Create blank list to store the applications that we will be moving forward with.
  $applications = [System.Collections.Generic.List[PSCustomObject]]@()  
  # What files should exist
  $AppFileNames = @("Deploy-Application.ps1", "Icon.png", "App.json")
  # Loop through each of the applications
  foreach ($application in $applicationList) {
    # Should the application be skipped
    $skip = $false
    # Where should the files exist
    $AppPackageFolderPath = Join-Path -Path $script:AppFactorySourceDir -ChildPath "Apps" -AdditionalChildPath $application.AppFolderName
    if ($script:AppFactoryLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Checking to ensure that all the appropriate files are present for the application at path <c='green'>$($($AppPackageFolderPath))</c>."  -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
    }
    # Check for the required files
    foreach ($AppFileName in $AppFileNames) {
      $AppFileNamePath = Join-Path -Path $AppPackageFolderPath -ChildPath $AppFileName
      if (-not(Test-Path -Path $AppFileNamePath)) {
        $skip = $true
        if ($script:AppFactoryLogging) {
          Write-PSFMessage -Message "[$($application.IntuneAppName)] File Not Found $($AppFileNamePath). Skipping Application." -Level "Error" -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "Application Factory Service"
        }
      }
      if ($script:AppFactoryLogging) {
        Write-PSFMessage -Message "[$($application.IntuneAppName)] <c='green'>$($AppFileName)</c> found"  -Level $LogLevel -Tag "Application", "$($application.IntuneAppName)" -Target "Application Factory Service"
      }
      if ($AppFileName -eq "App.json") {
        # Get Content of App.Json
        $AppFileContent = Get-Content -Path $AppFileNamePath | ConvertFrom-Json
        # If there are no detection rules
        if ($AppFileContent.DetectionRule.Count -eq 0) {
          $skip = $true
          if ($script:AppFactoryLogging) {
            Write-PSFMessage -Message "[$($application.IntuneAppName)] Could not find any detection rule defined, ensure App.json contains atleast one detection rule element. Skipping Application." -Level "Error" -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "Application Factory Service"
          }
        }
        # If there are rules, and set to script confirm Script File Exists
        if ($AppFileContent.DetectionRule.Count -eq 1) {
          if ($AppFileContent.DetectionRule.Type -like "Script") {
            $DetectionScriptFilePath = Join-Path -Path $AppPackageFolderPath -ChildPath $AppFileContent.DetectionRule.ScriptFile
            if (-not(Test-Path -Path $DetectionScriptFilePath)) {
              $skip = $true
              if ($script:AppFactoryLogging) {
                Write-PSFMessage -Message "[$($application.IntuneAppName)] Could not detect given detection script file in app folder. Skipping Application." -Level "Error" -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "Application Factory Service"
              }
            } 
          }
        }
        if ($AppFileContent.DetectionRule.Count -ge 2) {
          if ("Script" -in $AppFileContent.DetectionRule.Type) {
            $skip = $true
            if ($script:AppFactoryLogging) {
              Write-PSFMessage -Message "[$($application.IntuneAppName)] Multiple detection rule types are defined, where at least one of them are of type 'Script', which is not a supported configuration in Intune. Skipping Application." -Level "Error" -Tag "Application", "$($application.IntuneAppName)", "Error" -Target "Application Factory Service"
            }
          }
        }
        if ($script:AppFactoryLogging) {
          Write-PSFMessage -Message "[$($application.IntuneAppName)] <c='green'>Detection rule appears to be configured correctly</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
        }
        $content = Get-Content -Path $AppFileNamePath
        if($content -match "^.*`"<<.*>>`".*$"){
          $skip = $true
          if($script:AppFactoryLogging){
            Write-PSFMessage -Message "[$($application.IntuneAppName)] Not all fields that need to configure in App.JSON have been updated. Skipping Application." -Level "Error" -Tag "Application","$($application.IntuneAppName)","Error" -Target "Application Factory Service"
          }
        }
        if($script:AppFactoryLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] <c='green'>All fields in App.json appear to be configured correctly</c>" -Level $LogLevel -Tag "Application","$($application.IntuneAppName)" -Target "Application Factory Service"
        }      
      }        
    }    
    if(-not $skip){
      $applications.Add($application) | Out-Null
    }     
  }
  return $applications
}
#EndRegion '.\Public\Test-AppFactoryFiles.ps1' 92