ApplicationFactoryClient.psm1

#Region '.\Private\Get-AppFactoryClientAppDownload.ps1' 0
function Get-AppFactoryClientAppDownload{
  [CmdletBinding()]
  [OutputType([String])]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$sasTokens,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$workspace,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  $downloadFolder = Join-Path -Path $workspace -ChildPath "downloads" -AdditionalChildPath $application.IntuneAppName
  if(Test-Path $downloadFolder){
    Remove-Item -Path $downloadFolder -Force -Recurse -ErrorAction SilentlyContinue
  }
  New-Item -Path $downloadFolder -ItemType Directory | Out-Null
  if($script:AppFactoryClientLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Download Folder - <c='green'>$($downloadFolder)</c>" -Level $LogLevel -Tag "Applications","$($application.IntuneAppName)" -Target "Application Factory Client"
  }
  if($application.container -eq $sasTokens.PublicContainerName){
    $endpoint = "$($sasTokens.StoragePath)/$($sasTokens.PublicContainerName)/$($application.GUID)/$($application.AppVersion)"
    $sas = $sasTokens.public
  }
  else{
    $endpoint = "$($sasTokens.StoragePath)/$($sasTokens.OrganizationContainerName)/$($application.GUID)/$($application.AppVersion)"
    $sas = $sasTokens.organization      
  }
  if($script:AppFactoryClientLogging){
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading files from $($endpoint)" -Level $LogLevel -Tag "Applications","$($application.IntuneAppName)" -Target "Application Factory Client"
  }
  $filelist = @("$($application.IntuneAppName).intunewin","App.json")
  foreach($file in $filelist){
    if($script:AppFactoryClientLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Downloading file $($file)" -Level $LogLevel -Tag "Applications","$($application.IntuneAppName)" -Target "Application Factory Client"
    }
    try{
      Invoke-WebRequest -Uri "$($endpoint)/$($file)?$($sas)" -OutFile "$($downloadFolder)\$($file)" | Out-Null
    }
    catch{
      throw "[$($application.IntuneAppName)] Failed to download $($file) from $($baseURI). $($_.Exception.Message)"
    }
  }
  $AppData = Get-Content "$($downloadFolder)\App.json" | ConvertFrom-JSON
  $downloadFiles = [System.Collections.Generic.List[String]]@()
  $downloadFiles.Add($AppData.PackageInformation.IconFile) | Out-Null
  if($AppData.DetectionRule.Type -eq "Script"){
    $downloadFiles.Add($AppData.DetectionRule.ScriptFile) | Out-Null
  }
  foreach($file in $downloadFiles){
    try{
      Invoke-WebRequest -Uri "$($endpoint)/$($file)?$($sas)" -OutFile "$($downloadFolder)\$($file)" | Out-Null
    }
    catch{
      throw "[$($application.IntuneAppName)] Failed to download $($file) from $($baseURI). $($_.Exception.Message)"
    }
  }
  return $downloadFolder      
}
#EndRegion '.\Private\Get-AppFactoryClientAppDownload.ps1' 57
#Region '.\Private\New-AppFactoryClientApp.ps1' 0
function New-AppFactoryClientApp {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$tenantID,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$clientID,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$appregsecret,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  if ($script:AppFactoryClientLogging) {
    Write-PSFMessage -Message "[$($application.IntuneAppName)] Publishing Applications" -Level $LogLevel -Tag "Applications", "Intune", "$($application.IntuneAppName)" -Target "Application Factory Client"
  }
  $AppData = Get-Content -Path "$($application.filePath)\App.json" | ConvertFrom-JSON
  $IntuneAppPackage = Get-ChildItem "$($application.filePath)\*.intunewin"
  $AppIconFile = Join-Path -Path $application.filePath -ChildPath $AppData.PackageInformation.IconFile
  $ScriptsFolder = $application.filePath
  # Create default requirement rule
  $RequirementRule = New-IntuneWin32AppRequirementRule -Architecture $AppData.RequirementRule.Architecture -MinimumSupportedWindowsRelease $AppData.RequirementRule.MinimumSupportedWindowsRelease  
  # Create additional custom requirement rules
  $CustomRequirementRuleCount = ($AppData.CustomRequirementRule | Measure-Object).Count  
  if ($CustomRequirementRuleCount -ge 1) {
    $RequirementRules = New-AppFactoryClientCustomRequirements -RequirementRuleItems $AppData.CustomRequirementRule -application $application -ScriptsFolder $ScriptsFolder -loglevel $LogLevel
  }
  $DetectionRules = New-AppFactoryClientDetectionRule -DetectionRules $AppData.DetectionRule -application $application  -ScriptsFolder $ScriptsFolder -loglevel $LogLevel  
  # Icon File
  $Icon = New-IntuneWin32AppIcon -FilePath $AppIconFile
  # Construct a table of default parameters for Win32 app
  $Win32AppArgs = @{
    "FilePath"          = $IntuneAppPackage.FullName
    "DisplayName"       = "$($application.IntuneAppName) $($application.AppVersion)"
    "AppVersion"        = $AppData.Information.AppVersion
    "Description"       = $AppData.Information.Description
    "Publisher"         = $AppData.Information.Publisher
    "InstallExperience" = $AppData.Program.InstallExperience
    "RestartBehavior"   = $AppData.Program.DeviceRestartBehavior
    "DetectionRule"     = $DetectionRules
    "RequirementRule"   = $RequirementRule
    "Notes"             = "$($AppData.Information.Notes)`n`rSTSID:$($application.GUID)"
  } 
  # Dynamically add additional parameters for Win32 app
  if ($null -ne $RequirementRules) {
    $Win32AppArgs.Add("AdditionalRequirementRule", $RequirementRules)
  }
  if (Test-Path -Path $AppIconFile) {
    $Win32AppArgs.Add("Icon", $Icon)
  }
  if (-not([string]::IsNullOrEmpty($AppData.Information.InformationURL))) {
    $Win32AppArgs.Add("InformationURL", $AppData.Information.InformationURL)
  }  
  if (-not([string]::IsNullOrEmpty($AppData.Information.PrivacyURL))) {
    $Win32AppArgs.Add("PrivacyURL", $AppData.Information.PrivacyURL)
  }   
  if (-not([string]::IsNullOrEmpty($AppData.Information.Owner))) {
    $Win32AppArgs.Add("Owner", $AppData.Information.Owner)
  }
  if (-not([string]::IsNullOrEmpty($AppData.Program.InstallCommand))) {
    if($application.InteractiveInstall){
      $Win32AppArgs.Add("InstallCommandLine", $AppData.Program.InstallCommandInteractive)
    }
    else{
      $Win32AppArgs.Add("InstallCommandLine", $AppData.Program.InstallCommand)
    }
  }
  if (-not([string]::IsNullOrEmpty($AppData.Program.UninstallCommand))) {
    if($application.InteractiveUninstall){
      $Win32AppArgs.Add("UninstallCommandLine", $AppData.Program.UninstallCommandInteractive)
    }
    else{
      $Win32AppArgs.Add("UninstallCommandLine", $AppData.Program.UninstallCommand)
    }
  }
  if (-not([string]::IsNullOrEmpty($AppData.Program.AllowAvailableUninstall))) {
    if ($AppData.Program.AllowAvailableUninstall -eq $true) {
      $Win32AppArgs.Add("AllowAvailableUninstall", $true)
    }
  }
  try {
    Connect-MSIntuneGraph -TenantID $tenantID -ClientID $clientID -ClientSecret $appregsecret | Out-Null
    Add-IntuneWin32App @Win32AppArgs -UseAzCopy -ErrorAction Stop -WarningAction Stop | Out-Null
  }
  catch {
    $intuneApp = Get-GraphIntuneApp -displayName $application.IntuneAppName
    if ($intuneApp.count -gt 1) {
      $intuneApp = $intuneApp | Sort-Object -Property createdDateTime -Descending |  Select-Object -First 1
    }
    Remove-GraphIntuneApp -applicationid $intuneApp.id
    throw $_
  }  
}
#EndRegion '.\Private\New-AppFactoryClientApp.ps1' 90
#Region '.\Private\New-AppFactoryClientCustomRequirements.ps1' 0
function New-AppFactoryClientCustomRequirements{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$RequirementRuleItems,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$ScriptsFolder,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  $RequirementRules = [System.Collections.Generic.List[PSCustomObject]]@()
  foreach ($RequirementRuleItem in $RequirementRuleItems) {
    switch ($RequirementRuleItem.Type) {
      "File" {
        switch ($RequirementRuleItem.DetectionMethod) {
          "Existence" {
            # Create a custom file based requirement rule
            $RequirementRuleArgs = @{
              "Existence" = $true
              "Path" = $RequirementRuleItem.Path
              "FileOrFolder" = $RequirementRuleItem.FileOrFolder
              "DetectionType" = $RequirementRuleItem.DetectionType
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }
          "DateModified" {
            # Create a custom file based requirement rule
            $RequirementRuleArgs = @{
              "DateModified" = $true
              "Path" = $RequirementRuleItem.Path
              "FileOrFolder" = $RequirementRuleItem.FileOrFolder
              "Operator" = $RequirementRuleItem.Operator
              "DateTimeValue" = $RequirementRuleItem.DateTimeValue
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }
          "DateCreated" {
            # Create a custom file based requirement rule
            $RequirementRuleArgs = @{
              "DateCreated" = $true
              "Path" = $RequirementRuleItem.Path
              "FileOrFolder" = $RequirementRuleItem.FileOrFolder
              "Operator" = $RequirementRuleItem.Operator
              "DateTimeValue" = $RequirementRuleItem.DateTimeValue
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }
          "Version" {
            # Create a custom file based requirement rule
            $RequirementRuleArgs = @{
              "Version" = $true
              "Path" = $RequirementRuleItem.Path
              "FileOrFolder" = $RequirementRuleItem.FileOrFolder
              "Operator" = $RequirementRuleItem.Operator
              "VersionValue" = $RequirementRuleItem.VersionValue
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }
          "Size" {
            # Create a custom file based requirement rule
            $RequirementRuleArgs = @{
              "Size" = $true
              "Path" = $RequirementRuleItem.Path
              "FileOrFolder" = $RequirementRuleItem.FileOrFolder
              "Operator" = $RequirementRuleItem.Operator
              "SizeInMBValue" = $RequirementRuleItem.SizeInMBValue
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }                                
        }
        # Create file based requirement rule
        $CustomRequirementRule = New-IntuneWin32AppRequirementRuleFile @RequirementRuleArgs              
      } 
      "Registry" {
        switch ($RequirementRuleItem.DetectionMethod) {
          "Existence" {
            # Create a custom registry based requirement rule
            $RequirementRuleArgs = @{
              "Existence" = $true
              "KeyPath" = $RequirementRuleItem.KeyPath
              "ValueName" = $RequirementRuleItem.ValueName
              "DetectionType" = $RequirementRuleItem.DetectionType
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }
          "StringComparison" {
            # Create a custom registry based requirement rule
            $RequirementRuleArgs = @{
              "StringComparison" = $true
              "KeyPath" = $RequirementRuleItem.KeyPath
              "ValueName" = $RequirementRuleItem.ValueName
              "StringComparisonOperator" = $RequirementRuleItem.Operator
              "StringComparisonValue" = $RequirementRuleItem.Value
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }
          "VersionComparison" {
            # Create a custom registry based requirement rule
            $RequirementRuleArgs = @{
              "VersionComparison" = $true
              "KeyPath" = $RequirementRuleItem.KeyPath
              "ValueName" = $RequirementRuleItem.ValueName
              "VersionComparisonOperator" = $RequirementRuleItem.Operator
              "VersionComparisonValue" = $RequirementRuleItem.Value
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }
          "IntegerComparison" {
            # Create a custom registry based requirement rule
            $RequirementRuleArgs = @{
              "IntegerComparison" = $true
              "KeyPath" = $RequirementRuleItem.KeyPath
              "ValueName" = $RequirementRuleItem.ValueName
              "IntegerComparisonOperator" = $RequirementRuleItem.Operator
              "IntegerComparisonValue" = $RequirementRuleItem.Value
              "Check32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.Check32BitOn64System)
            }
          }
        }
        # Create registry based requirement rule
        $CustomRequirementRule = New-IntuneWin32AppRequirementRuleRegistry @RequirementRuleArgs
      }
      "Script" {
        switch ($RequirementRuleItem.DetectionMethod) {
          "StringOutput" {
            # Create a custom script based requirement rule
            $RequirementRuleArgs = @{
              "StringOutputDataType" = $true
              "ScriptFile" = (Join-Path -Path $ScriptsFolder -ChildPath $RequirementRuleItem.ScriptFile)
              "ScriptContext" = $RequirementRuleItem.ScriptContext
              "StringComparisonOperator" = $RequirementRuleItem.Operator
              "StringValue" = $RequirementRuleItem.Value
              "RunAs32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.RunAs32BitOn64System)
              "EnforceSignatureCheck" = [System.Convert]::ToBoolean($RequirementRuleItem.EnforceSignatureCheck)
            }
          }
          "IntegerOutput" {
            # Create a custom script based requirement rule
            $RequirementRuleArgs = @{
              "IntegerOutputDataType" = $true
              "ScriptFile" = $RequirementRuleItem.ScriptFile
              "ScriptContext" = $RequirementRuleItem.ScriptContext
              "IntegerComparisonOperator" = $RequirementRuleItem.Operator
              "IntegerValue" = $RequirementRuleItem.Value
              "RunAs32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.RunAs32BitOn64System)
              "EnforceSignatureCheck" = [System.Convert]::ToBoolean($RequirementRuleItem.EnforceSignatureCheck)
            }
          }
          "BooleanOutput" {
            # Create a custom script based requirement rule
            $RequirementRuleArgs = @{
              "BooleanOutputDataType" = $true
              "ScriptFile" = $RequirementRuleItem.ScriptFile
              "ScriptContext" = $RequirementRuleItem.ScriptContext
              "BooleanComparisonOperator" = $RequirementRuleItem.Operator
              "BooleanValue" = $RequirementRuleItem.Value
              "RunAs32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.RunAs32BitOn64System)
              "EnforceSignatureCheck" = [System.Convert]::ToBoolean($RequirementRuleItem.EnforceSignatureCheck)
            }
          }
          "DateTimeOutput" {
            # Create a custom script based requirement rule
            $RequirementRuleArgs = @{
              "DateTimeOutputDataType" = $true
              "ScriptFile" = $RequirementRuleItem.ScriptFile
              "ScriptContext" = $RequirementRuleItem.ScriptContext
              "DateTimeComparisonOperator" = $RequirementRuleItem.Operator
              "DateTimeValue" = $RequirementRuleItem.Value
              "RunAs32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.RunAs32BitOn64System)
              "EnforceSignatureCheck" = [System.Convert]::ToBoolean($RequirementRuleItem.EnforceSignatureCheck)
            }
          }
          "FloatOutput" {
            # Create a custom script based requirement rule
            $RequirementRuleArgs = @{
              "FloatOutputDataType" = $true
              "ScriptFile" = $RequirementRuleItem.ScriptFile
              "ScriptContext" = $RequirementRuleItem.ScriptContext
              "FloatComparisonOperator" = $RequirementRuleItem.Operator
              "FloatValue" = $RequirementRuleItem.Value
              "RunAs32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.RunAs32BitOn64System)
              "EnforceSignatureCheck" = [System.Convert]::ToBoolean($RequirementRuleItem.EnforceSignatureCheck)
            }
          }
          "VersionOutput" {
            # Create a custom script based requirement rule
            $RequirementRuleArgs = @{
              "VersionOutputDataType" = $true
              "ScriptFile" = $RequirementRuleItem.ScriptFile
              "ScriptContext" = $RequirementRuleItem.ScriptContext
              "VersionComparisonOperator" = $RequirementRuleItem.Operator
              "VersionValue" = $RequirementRuleItem.Value
              "RunAs32BitOn64System" = [System.Convert]::ToBoolean($RequirementRuleItem.RunAs32BitOn64System)
              "EnforceSignatureCheck" = [System.Convert]::ToBoolean($RequirementRuleItem.EnforceSignatureCheck)
            }
          }
        }
        # Create script based requirement rule
        $CustomRequirementRule = New-IntuneWin32AppRequirementRuleScript @RequirementRuleArgs
      }                       
    }
    # Add requirement rule to list
    $RequirementRules.Add($CustomRequirementRule) | Out-Null      
  }
  return $RequirementRules
}
#EndRegion '.\Private\New-AppFactoryClientCustomRequirements.ps1' 206
#Region '.\Private\New-AppFactoryClientDetectionRule.ps1' 0
function New-AppFactoryClientDetectionRule{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$DetectionRules,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][PSCustomObject]$application,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$ScriptsFolder,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )  
  $rules = [System.Collections.Generic.List[PSCustomObject]]@() 
  foreach ($DetectionRuleItem in $DetectionRules) {
    switch ($DetectionRuleItem.Type) {
      "MSI" {
        if($script:AppFactoryClientLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Adding MSI Detection Rule" -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
        }
        # Create a MSI installation based detection rule
        $DetectionRuleArgs = @{
          "ProductCode" = $DetectionRuleItem.ProductCode
          "ProductVersionOperator" = $DetectionRuleItem.ProductVersionOperator
        }
        if (-not([string]::IsNullOrEmpty($DetectionRuleItem.ProductVersion))) {
          $DetectionRuleArgs.Add("ProductVersion", $DetectionRuleItem.ProductVersion)
        }
        # Create MSI based detection rule
        $DetectionRule = New-IntuneWin32AppDetectionRuleMSI @DetectionRuleArgs
      } 
      "Script" {
        if($script:AppFactoryClientLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Adding Script Detection Rule" -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
        }
        # Create a PowerShell script based detection rule
        $DetectionRuleArgs = @{
          "ScriptFile" = (Join-Path -Path $ScriptsFolder -ChildPath $DetectionRuleItem.ScriptFile)
          "EnforceSignatureCheck" = [System.Convert]::ToBoolean($DetectionRuleItem.EnforceSignatureCheck)
          "RunAs32Bit" = [System.Convert]::ToBoolean($DetectionRuleItem.RunAs32Bit)
        }
        # Create script based detection rule
        $DetectionRule = New-IntuneWin32AppDetectionRuleScript @DetectionRuleArgs
      } 
      "Registry" {
        if($script:AppFactoryClientLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Adding Registry Detection Rule" -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
        }
        switch ($DetectionRuleItem.DetectionMethod) {
          "Existence" {
            # Construct registry existence detection rule parameters
            $DetectionRuleArgs = @{
              "Existence" = $true
              "KeyPath" = $DetectionRuleItem.KeyPath
              "DetectionType" = $DetectionRuleItem.DetectionType
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
            if (-not([string]::IsNullOrEmpty($DetectionRuleItem.ValueName))) {
              $DetectionRuleArgs.Add("ValueName", $DetectionRuleItem.ValueName)
            }
          }
          "VersionComparison" {
            # Construct registry version comparison detection rule parameters
            $DetectionRuleArgs = @{
              "VersionComparison" = $true
              "KeyPath" = $DetectionRuleItem.KeyPath
              "ValueName" = $DetectionRuleItem.ValueName
              "VersionComparisonOperator" = $DetectionRuleItem.Operator
              "VersionComparisonValue" = $DetectionRuleItem.Value
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
          }
          "StringComparison" {
            # Construct registry string comparison detection rule parameters
            $DetectionRuleArgs = @{
              "StringComparison" = $true
              "KeyPath" = $DetectionRuleItem.KeyPath
              "ValueName" = $DetectionRuleItem.ValueName
              "StringComparisonOperator" = $DetectionRuleItem.Operator
              "StringComparisonValue" = $DetectionRuleItem.Value
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
          }
          "IntegerComparison" {
            # Construct registry integer comparison detection rule parameters
            $DetectionRuleArgs = @{
              "IntegerComparison" = $true
              "KeyPath" = $DetectionRuleItem.KeyPath
              "ValueName" = $DetectionRuleItem.ValueName
              "IntegerComparisonOperator" = $DetectionRuleItem.Operator
              "IntegerComparisonValue" = $DetectionRuleItem.Value
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
          }
        }
        # Create registry based detection rule
        $DetectionRule = New-IntuneWin32AppDetectionRuleRegistry @DetectionRuleArgs        
      }
      "File" {
        if($script:AppFactoryClientLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Adding File Detection Rule" -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
        }
        switch ($DetectionRuleItem.DetectionMethod) {
          "Existence" {
            # Create a custom file based requirement rule
            $DetectionRuleArgs = @{
              "Existence" = $true
              "Path" = $DetectionRuleItem.Path
              "FileOrFolder" = $DetectionRuleItem.FileOrFolder
              "DetectionType" = $DetectionRuleItem.DetectionType
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
          }
          "DateModified" {
            # Create a custom file based requirement rule
            $DetectionRuleArgs = @{
              "DateModified" = $true
              "Path" = $DetectionRuleItem.Path
              "FileOrFolder" = $DetectionRuleItem.FileOrFolder
              "Operator" = $DetectionRuleItem.Operator
              "DateTimeValue" = $DetectionRuleItem.DateTimeValue
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
          }
          "DateCreated" {
            # Create a custom file based requirement rule
            $DetectionRuleArgs = @{
              "DateCreated" = $true
              "Path" = $DetectionRuleItem.Path
              "FileOrFolder" = $DetectionRuleItem.FileOrFolder
              "Operator" = $DetectionRuleItem.Operator
              "DateTimeValue" = $DetectionRuleItem.DateTimeValue
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
          }
          "Version" {
            # Create a custom file based requirement rule
            $DetectionRuleArgs = @{
              "Version" = $true
              "Path" = $DetectionRuleItem.Path
              "FileOrFolder" = $DetectionRuleItem.FileOrFolder
              "Operator" = $DetectionRuleItem.Operator
              "VersionValue" = $DetectionRuleItem.VersionValue
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
          }
          "Size" {
            # Create a custom file based requirement rule
            $DetectionRuleArgs = @{
              "Size" = $true
              "Path" = $DetectionRuleItem.Path
              "FileOrFolder" = $DetectionRuleItem.FileOrFolder
              "Operator" = $DetectionRuleItem.Operator
              "SizeInMBValue" = $DetectionRuleItem.SizeInMBValue
              "Check32BitOn64System" = [System.Convert]::ToBoolean($DetectionRuleItem.Check32BitOn64System)
            }
          }
        }
        # Create file based detection rule
        $DetectionRule = New-IntuneWin32AppDetectionRuleFile @DetectionRuleArgs
      }      
    }
    # Add detection rule to list
    $rules.Add($DetectionRule) | Out-Null    
  }
  return $rules    
}
#EndRegion '.\Private\New-AppFactoryClientDetectionRule.ps1' 164
#Region '.\Public\Add-AppFactoryClientAppAssignments.ps1' 0
function Add-AppFactoryClientAppAssignments {
  [CmdletBinding()]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$intuneApplications,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"  
  )  
  foreach ($application in $applicationList) {
    if ($script:AppFactoryClientLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Adding Application Assignments" -Level $LogLevel -Tag "Applications", "Intune", "$($application.IntuneAppName)" -Target "Application Factory Client"
    }
    $applications = $intuneApplications | Where-Object { $_.Notes -match "STSID:$($application.GUID)" } |  Sort-Object -Property "createdDateTime" -Descending | Select-Object -first 1
    $workingApplication = $applications[0]
    $commonVariables = @{
      "applicationid" = $workingApplication.id
      "filters"       = $application.filters
    }
    try {
      if ($application.AvailableAssignments -ne "") { Add-GraphIntuneAppAssignment @commonVariables -intent available -groups $application.AvailableAssignments -foreground $application.foreground }
      if ($application.AvailableExceptions -ne "") { Add-GraphIntuneAppAssignment @commonVariables -intent available -groups $application.AvailableExceptions -exclude }
      if ($application.RequiredAssignments -ne "") { Add-GraphIntuneAppAssignment @commonVariables -intent required -groups $application.RequiredAssignments -foreground $application.foreground }
      if ($application.RequiredExceptions -ne "") { Add-GraphIntuneAppAssignment @commonVariables -intent required -groups $application.RequiredExceptions -exclude }
      if ($application.UninstallAssignments -ne "") { Add-GraphIntuneAppAssignment @commonVariables -intent uninstall -groups $application.UninstallAssignments -foreground $application.foreground }
      if ($application.UninstallExceptions -ne "") { Add-GraphIntuneAppAssignment @commonVariables -intent uninstall -groups $application.UninstallExceptions -exclude }
    }
    catch {
      $_
    }
  }
}
#EndRegion '.\Public\Add-AppFactoryClientAppAssignments.ps1' 31
#Region '.\Public\Add-AppFactoryClientESPAssignment.ps1' 0
function Add-AppFactoryClientESPAssignment{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$intuneApplications,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  # Loop through applications
  foreach($application in $applicationList){
    if($application.espprofiles){
      
      $applications = $intuneApplications | Where-Object {$_.Notes -eq "STSID:$($application.guid)"} | Sort-Object -Property @{expr={[system.version]$_.DisplayVersion}},createdDateTime -Descending
      foreach($esp in $application.espprofiles){
        if($script:AppFactoryClientLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Assigning application to ESP Porfile $($esp)" -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
        }
        Add-GraphIntuneAppAddToESP -displayName $esp -applicationid $applications[0].id
      }
    }
  }  
}
#EndRegion '.\Public\Add-AppFactoryClientESPAssignment.ps1' 23
#Region '.\Public\Compare-AppFactoryClientAppVersions.ps1' 0
function Compare-AppFactoryClientAppVersions {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$intuneApplications,
    [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 the applications
  foreach ($application in $applicationList) {
    if ($script:AppFactoryClientLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Comparing Versions between expected and latest published" -Level $LogLevel -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client"
    }
    $intuneApplication = $intuneApplications | Where-Object { $_.Notes -eq "STSID:$($application.GUID)" } |  Sort-Object -Property "createdDateTime" -Descending | Select-Object -first 1
    if ($script:AppFactoryClientLogging) {
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Current Published Version: <c='green'>$($intuneApplication.displayVersion)</c>" -Level $LogLevel -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client"
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Current Expected Version: <c='green'>$($application.AppVersion)</c>" -Level $LogLevel -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client"
    } 
    if (-not $intuneApplication -or $force -or [System.Version]$application.AppVersion -gt [System.Version]$intuneApplication.displayVersion) {
      if ($script:AppFactoryClientLogging) {
        if ($force) {
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Current Expected Version already published. But force selected." -Level $LogLevel -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client"
        }
        else {
          Write-PSFMessage -Message "[$($application.IntuneAppName)]Application requires an update." -Level $LogLevel -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client"
        }
      }
      $applications.Add($application) | Out-Null        
    }  
    else {
      if ($script:AppFactoryClientLogging) {
        Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Current Expected Version is already present.</c>" -Level $LogLevel -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client"
        Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Removing from process.</c>" -Level $LogLevel -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client"
      }
    }     
  }
  return $applications   
}
#EndRegion '.\Public\Compare-AppFactoryClientAppVersions.ps1' 42
#Region '.\Public\Copy-AppFactoryClientAppAssignments.ps1' 0
function Copy-AppFactoryClientAppAssignments{
  [CmdletBinding()]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$intuneApplications,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"  
  )  
  foreach($application in $applicationList){
    if($application.CopyPrevious){
      if($script:AppFactoryClientLogging){
        Write-PSFMessage -Message "[$($application.IntuneAppName)] Copying Application Assignments" -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
      }
      $applications = $intuneApplications | Where-Object { $_.Notes -match "STSID:$($application.GUID)" } |  Sort-Object -Property "createdDateTime" -Descending
      if($applications.count -gt 1){
        Copy-GraphIntuneAppAssignments -applicationid $applications[0].id -copyapplicationid $applications[1].id
      }
    }
  }    
}
#EndRegion '.\Public\Copy-AppFactoryClientAppAssignments.ps1' 20
#Region '.\Public\Get-AppFactoryClientApp.ps1' 0
function Get-AppFactoryClientApp{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter()][string]$GUID,
    [Parameter()][string]$AppName,
    [Parameter()][bool]$AddToIntune,
    [Parameter()][string]$AvailableAssignments,
    [Parameter()][string]$AvailableExceptions,
    [Parameter()][string]$RequiredAssignments,
    [Parameter()][string]$RequiredExceptions,
    [Parameter()][string]$UninstallAssignments,
    [Parameter()][string]$UninstallExceptions,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  # App Folders
  $applicationFolderPath = Join-Path -Path $script:AppFactoryClientSourceDir -ChildPath "Apps"
  # Get All Applications
  $applicationConfigFiles = Get-Childitem -Path $applicationFolderPath
  # Blank List for the Apps
  $applicationList =  [System.Collections.Generic.List[PSCustomObject]]@()
  # Loop through the configuration and add to the list.
  foreach($file in $applicationConfigFiles){
    $json = Get-Content $file.FullName | ConvertFrom-Json
    if($GUID -and $json.GUID -ne $GUID){continue}
    if($AppName -and $json.AppName -ne $AppName){continue}
    if($AddToIntune -and $json.AddToIntune -ne $AddToIntune){continue}
    if($AvailableAssignments -and $AvailableAssignments -notin $json.AvailableAssignments){continue}
    if($AvailableExceptions -and $AvailableExceptions -notin $json.AvailableExceptions){continue}
    if($RequiredAssignments -and $RequiredAssignments -notin $json.RequiredAssignments){continue}
    if($RequiredExceptions -and $RequiredExceptions -notin $json.RequiredExceptions){continue}
    if($UninstallAssignments -and $UninstallAssignments -notin $json.UninstallAssignments){continue}
    if($UninstallExceptions -and $UninstallExceptions -notin $json.UninstallExceptions){continue}
    $applicationList.Add($json) | Out-Null
  }  
  return $applicationList  
}
#EndRegion '.\Public\Get-AppFactoryClientApp.ps1' 38
#Region '.\Public\Get-AppFactoryClientAppFiles.ps1' 0
function Get-AppFactoryClientAppFiles {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter()][switch]$multithread,
    [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]]@()
  # App List Endpoint
  $endpoint = "$($script:AppFactoryClientApiEndpoint)/SAS"
  if ($script:AppFactoryClientLogging) {
    Write-PSFMessage -Message "Getting SAS Token fron endoiunt $($endpoint)" -Level $LogLevel -Tag "Applications", "API", "SAS" -Target "Application Factory Client"
  }
  $headers = @{
    "content-type"  = "application/json"
    "authorization" = "bearer $(ConvertFrom-SecureString $script:AppFactoryClientAPISecret -AsPlainText)"
  }  
  $sasTokens = Invoke-RestMethod -Method "GET" -Uri $endpoint -Headers $headers -StatusCodeVariable "statusCode"
  if (-not $multithread.IsPresent) {
    foreach ($application in $applicationList) {
      try {
        $download = Get-AppFactoryClientAppDownload -application $application -sasTokens $sasTokens -workspace $script:AppFactoryClientWorkspace -LogLevel $LogLevel
        $application | Add-Member -MemberType "NoteProperty" -Name "filePath" -value $download -Force
        $applications.add($application) | Out-Null
      }
      catch {
        if ($script:AppFactoryClientLogging) {
          Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] failed download files.</c>" -Level Error -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client"
          Write-PSFMessage -Message "<c='yellow'>[$($application.IntuneAppName)] Error $($_).</c>" -Level Error -Tag "Applications", "$($application.IntuneAppName)" -Target "Application Factory Client" 
        }
      }
    }    
  }
  else {
    $applist = [hashtable]::Synchronized(@{})
    if ($script:LocalModulePath) {
      $modulePath = $script:LocalModulePath
    }
    else {
      $modulePath = (Get-module ApplicationFactoryClient).path
    }
    $applicationList | Foreach-Object -Parallel {
      Import-Module -Name $using:modulePath -Force
      $application = $_
      $dict = $using:applist
      $script:AppFactoryClientLogging = $using:script:AppFactoryClientLogging
      $script:AppFactoryClientWorkspace = $using:script:AppFactoryClientWorkspace
      $sasTokens = $using:sasTokens
      $LogLevel = $using:LogLevel
      try {
        $download = Get-AppFactoryClientAppDownload -application $application -sasTokens $sasTokens -workspace $script:AppFactoryClientWorkspace -LogLevel $LogLevel
        $obj = [PSCustomObject]@{
          "Status"   = "Downloaded"
          "download" = $download
          "Error"    = $null
        }
        $dict.add($application.GUID, $obj)         
      }
      catch {
        $obj = [PSCustomObject]@{
          "Status"      = "Fail"
          "Application" = $null
          "Error"       = $_
        }
        $dict.add($application.GUID, $obj)
      }      
    }
    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 'filePath' -Value $item.Value.download
        $applications.Add($application) | Out-Null
      }
    }    
  }
  return $applications
}
#EndRegion '.\Public\Get-AppFactoryClientAppFiles.ps1' 81
#Region '.\Public\Get-AppFactoryClientAppList.ps1' 0
function Get-AppFactoryClientAppList {
  [CmdletBinding()]
  param(
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"    
  )
  # Where to save file
  $ApplicationFolder = Join-Path -Path $script:AppFactoryClientSourceDir -ChildPath "Apps" 
  if(-not (Test-Path -Path $ApplicationFolder)){
    New-Item -Path $ApplicationFolder -ItemType Directory | Out-Null
  }
  $endpoint = "$($script:AppFactoryClientApiEndpoint)/AppList"    
  if ($script:AppFactoryClientLogging) {
    Write-PSFMessage -Message "Storing application configuration files to <c='green'>$($ApplicationFolder)</c>" -Level $LogLevel -Tag "Applications" -Target "Application Factory Client"
    Write-PSFMessage -Message "Downloading app list file from <c='green'>$($endpoint)</c>" -Level $LogLevel -Tag "Application Factory Client"
  }
  # Retrieve Data
  $headers = @{
    "content-type"  = "application/json"
    "authorization" = "bearer $(ConvertFrom-SecureString $script:AppFactoryClientAPISecret -AsPlainText)"
  }
  $applicationList = Invoke-RestMethod -Method "GET" -Uri $endpoint -Headers $headers -StatusCodeVariable "statusCode"
  foreach($application in $applicationList){
    if($script:AppFactoryClientLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Writing application configuration" -Level $LogLevel -Tag "Applications","$($application.IntuneAppName)" -Target "Application Factory Client"
    }
    if(($application.filters.psobject.properties.value)){
      $filters = $application.filters -split "@@"
      $appfilters = @{}
      foreach($filter in $filters){
        $temp = $filter -split ";"
        $appfilters.add($temp[0],@{
          "filterType" = $temp[1]
          "filterName" = $temp[2]
        })
      }
      $application.filters = $appfilters
    }
    $applicationfile = Join-Path -Path $ApplicationFolder -ChildPath "$($application.IntuneAppName).json"
    $application | ConvertTo-Json -Depth 5 | Out-File -Path $applicationfile
  }  
}
#EndRegion '.\Public\Get-AppFactoryClientAppList.ps1' 42
#Region '.\Public\Publish-AppFactoryClientApp.ps1' 0
function Publish-AppFactoryClientApp{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter()][switch]$multithread,
    [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]]@()
  if(-not $multithread.IsPresent){
    foreach($application in $applicationList){
      try{
        New-AppFactoryClientApp -application $application -tenantID $script:AppFactoryClientTenantID -clientID $script:AppFactoryClientClientID -appregsecret $script:AppFactoryClientAppRegSecret -LogLevel $LogLevel
        $applications.add($application) | Out-Null
      }
      catch{
        if($script:AppFactoryClientLogging){
          Write-PSFMessage -Message "[$($application.IntuneAppName)] failed to create intune application." -Level Error -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
          Write-PSFMessage -Message "[$($application.IntuneAppName)] Error $($_)." -Level Error -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
        }
      }      
    }    
  }
  else{
    $applist = [hashtable]::Synchronized(@{})
    if ($script:LocalModulePath) {
      $modulePath = $script:LocalModulePath
    }
    else {
      $modulePath = (Get-module ApplicationFactoryClient).path
    }
    $applicationList | Foreach-Object -Parallel {
      Import-Module -Name $using:modulePath -Force
      $application = $_
      $dict = $using:applist
      $script:AppFactoryClientLogging = $using:script:AppFactoryClientLogging
      $script:AppFactoryClientTenantID = $using:script:AppFactoryClientTenantID
      $script:AppFactoryClientClientID = $using:script:AppFactoryClientClientID
      $script:AppFactoryClientAppRegSecret = $using:script:AppFactoryClientAppRegSecret
      $LogLevel = $using:LogLevel
      try{
        New-AppFactoryClientApp -application $application -tenantID $script:AppFactoryClientTenantID -clientID $script:AppFactoryClientClientID -appregsecret $script:AppFactoryClientAppRegSecret -LogLevel $LogLevel
        $obj = [PSCustomObject]@{
          "Status" = "Published"
          "Error" = $null
        }
        $dict.add($application.GUID,$obj)
      }
      catch{
        $obj = [PSCustomObject]@{
          "Status" = "Error"
          "Error" = $_
        }
        $dict.add($application.GUID,$obj)
      }
    }  
    foreach($item in $applist.GetEnumerator()){
      if($item.Value.status -eq "Published"){
        $index = $applicationList.guid.indexOf($item.key)
        $application = $applicationList[$index]
        $applications.Add($application) | Out-Null
      }
    }
  }
  return $applications
}
#EndRegion '.\Public\Publish-AppFactoryClientApp.ps1' 68
#Region '.\Public\Remove-AppFactoryClientApp.ps1' 0
function Remove-AppFactoryClientApp{
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(  
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$applicationList,
    [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.Collections.Generic.List[PSCustomObject]]$intuneApplications,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"  
  )
  # Loop through the applications and remove ones that are no longer required
  foreach($application in $applicationList){
    if($script:AppFactoryClientLogging){
      Write-PSFMessage -Message "[$($application.IntuneAppName)] Removing Previous Applications" -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
    }
    $applications = $intuneApplications | Where-Object {$_.Notes -match "STSID:$($application.guid)"} | Sort-Object -Property "createdDateTime"
    if($applications.count -gt $(1 + $application.KeepPrevious)){
      $removalCount = $applications.count - $application.KeepPrevious - 1
      if($script:AppFactoryClientLogging){
        Write-PSFMessage -Message "[$($application.IntuneAppName)] Based on keeping $($application.KeepPrevious) previous versions there are $($removalCount) applications to remove." -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
      }
      for($i = 0; $i -lt $removalCount; $i++){
        Remove-GraphIntuneApp -applicationid $applications[$i].id
      }
    }
    else{
      if($script:AppFactoryClientLogging){
        Write-PSFMessage -Message "[$($application.IntuneAppName)] Based on keeping $($application.KeepPrevious) previous versions there are no applications to remove." -Level $LogLevel -Tag "Applications","Intune","$($application.IntuneAppName)" -Target "Application Factory Client"
      }
    }
  }  
}
#EndRegion '.\Public\Remove-AppFactoryClientApp.ps1' 31
#Region '.\Public\Remove-AppFactoryClientFiles.ps1' 0
function Remove-AppFactoryClientFiles{
  [CmdletBinding()]
  param(
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"
  )
  if($script:AppFactoryClientLogging){
    Write-PSFMessage -Message "Removing previous workspace folder located at <c='green'>$($script:AppFactoryClientWorkspace)</c>" -Level $LogLevel -Tag "Setup" -Target "Application Factory Client"
  }
  if(Test-Path $script:AppFactoryClientWorkspace){
    Remove-Item -Path $script:AppFactoryClientWorkspace -Force -Recurse 
  }
}
#EndRegion '.\Public\Remove-AppFactoryClientFiles.ps1' 13
#Region '.\Public\Start-AppFactoryClient.ps1' 0
function Start-AppFactoryClient {
  [CmdletBinding()]
  [OutputType([System.Collections.Generic.List[PSCustomObject]])]
  param(
    [Parameter(Mandatory = $true, ParameterSetName = 'console')][ValidateNotNullOrEmpty()][string]$Path,
    [Parameter(Mandatory = $true, ParameterSetName = 'PSU')][switch]$PSU,
    [Parameter()][ValidateNotNullOrEmpty()][string]$configfile = "Configuration.json",
    [Parameter()][ValidateNotNullOrEmpty()][string]$apikeysecret,
    [Parameter()][string]$LocalModule = $null,
    [Parameter()][switch]$EnableLogging,
    [Parameter()][ValidateSet("Output", "Verbose")][string]$LogLevel = "Verbose"
  )
  # Set if we will be logging via PSFramework
  if ($EnableLogging) {
    $script:AppFactoryClientLogging = $true
  }
  else {
    $script:AppFactoryClientLogging = $false
  }
  # Create Global Variables
  if ($PSCmdlet.ParameterSetName -eq "console") {
    $script:AppFactoryClientSourceDir = $Path
  }
  if ($LocalModule) {
    $script:LocalModulePath = $LocalModule
  }
  # Where is the configuration file stored
  $script:AppFactoryClientConfigDir = Join-Path -Path $script:AppFactoryClientSourceDir -ChildPath "Configurations" 
  # Other Tool Directories
  $script:AppFactoryClientWorkspace = Join-Path -Path $script:AppFactoryClientSourceDir -ChildPath "Workspace"
  if ($script:AppFactoryClientLogging) {
    $AppFactoryClientLogDir = Join-Path -Path $script:AppFactoryClientSourceDir -ChildPath "Logs"  
    # Name for the log file to be used
    $logFile = "$($AppFactoryClientLogDir)\AppFactoryClient-%Date%.csv"  
    $paramSetPSFLoggingProvider = @{
      Name         = "logfile"
      InstanceName = "AppFactoryClient"
      FilePath     = $logFile
      Enabled      = $true
      Wait         = $true
    }
    Set-PSFLoggingProvider @paramSetPSFLoggingProvider 
    Write-PSFMessage -Message "Logging Configured" -Level $LogLevel -Tag "Setup" -Target "Application Factory Client"
    Write-PSFMessage -Message "Log File: <c='green'>$($logFile)</c>" -Level $LogLevel -Tag "Setup" -Target "AppFactoryClient"
    Write-PSFMessage -Message "Reading Configuration File" -Level $LogLevel -Tag "Setup" -Target "AppFactoryClient"
  }
  if ($PSCmdlet.ParameterSetName -eq "console") {
    try {
      $configFilePath = Join-Path $script:AppFactoryClientConfigDir -ChildPath $configfile
      $configDetails = Get-Content -Path $configFilePath -ErrorAction Stop | ConvertFrom-JSON
      $script:AppFactoryClientClientID = $configDetails.clientID
      $script:AppFactoryClientTenantID = $configDetails.tenantID
      $script:AppFactoryClientApiEndpoint = $configDetails.apiendpoint
      $script:AppFactoryClientAppRegSecret = Get-Secret -Vault $configDetails.keyVault -Name $configDetails.appregistrationSecret -AsPlainText
      $script:AppFactoryClientAPISecret = Get-Secret -Vault $configDetails.keyVault -Name $configDetails.apisecret
    }
    catch {
      throw $_
    }    
  }
  else {
    $script:AppFactoryClientAppRegSecret = ConvertTo-SecureString $secret:AppFactoryClientAppRegSecret -AsPlainText -Force
    $script:AppFactoryClientAPISecret = ConvertTo-SecureString $secret:AppFactoryClientAPISecret -AsPlainText -Force
  }
  return [PSCustomObject]@{
    AppFactoryClientClientID = $script:AppFactoryClientClientID
    AppFactoryClientTenantID = $script:AppFactoryClientTenantID
    AppFactoryClientAppRegSecret = $script:AppFactoryClientAppRegSecret

  }
}
#EndRegion '.\Public\Start-AppFactoryClient.ps1' 72
#Region '.\Public\Start-AppFactoryClientProcess.ps1' 0
function Start-AppFactoryClientProcess{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true,ParameterSetName = 'console')][ValidateNotNullOrEmpty()][string]$Path,
    [Parameter()][ValidateNotNullOrEmpty()][string]$configfile = "Configuration.json",
    [Parameter()][string]$LocalModule = $null,
    [Parameter()][switch]$EnableLogging,
    [Parameter()][switch]$multithread,
    [Parameter()][switch]$force,
    [Parameter()][switch]$LocalConfigs,
    [Parameter()][ValidateSet("Output","Verbose")][string]$LogLevel = "Verbose"    
  )  
  $processparams = @{
    "Path" = $Path
    "configfile" = $ConfigFile
    "LocalModule" = $LocalModule
    "EnableLogging" = $EnableLogging
    "LogLevel" = $LogLevel
  }  
  $multithreadParam = @{
    "multithread" = $multithread
  }
  $forceParam = @{
    "force" = $force
  }
  $graphDetails = Start-AppFactoryClient @processparams
  # Get the current application configurations from the cloud
  if(-not ($LocalConfigs.IsPresent)){
    Get-AppFactoryClientAppList
  }  
  for ($x = 0; $x -lt 5; $x++) {
    # Cleanup Previous Files
    Remove-AppFactoryClientFiles
    Write-Host "Getting list of applications configurations from service" -ForegroundColor Green
    $applicationList = Get-AppFactoryClientApp -AddToIntune $true -LogLevel $LogLevel
    Write-Host "There are $($applicationList.count) application set to be configured in intune" -ForegroundColor Green
    Write-Host "Getting Graph Token" -ForegroundColor Green
    Get-GraphAccessToken -clientID $graphDetails.AppFactoryClientClientID -tenantID $graphDetails.AppFactoryClientTenantID  -clientSecret $graphDetails.AppFactoryClientAppRegSecret | Out-Null
    Write-Host "Getting list of applications that are in intune" -ForegroundColor Green
    $intuneApplications = Get-GraphIntuneApp -type "microsoft.graph.win32LobApp"
    Write-Host "Compare expected version of application against intune" -ForegroundColor Green
    $applicationList = Compare-AppFactoryClientAppVersions -applicationList $applicationList -intuneapplications $intuneApplications -LogLevel $LogLevel @forceParam 
    Write-Host "There are $($applicationList.count) apps that require an update." -ForegroundColor Green
    if ($applicationList.count -eq 0) {
      Write-Host "No applications require an update" -ForegroundColor Green
      return
    }
    # Make a note of how many applications we are expecting to update
    $originalCount = $applicationList.count
    $applicationList = Get-AppFactoryClientAppFiles -applicationList $applicationList -LogLevel $LogLevel @multithreadParam
    Write-Host "Downloaded files for $($applicationList.count) apps that require an update." -ForegroundColor Green
    $applicationList = Publish-AppFactoryClientApp -applicationList $applicationList -LogLevel $LogLevel @multithreadParam
    Write-Host "Published files for $($applicationList.count) apps that require an update." -ForegroundColor Green
    Write-Host "Getting fresh list of applications that are in intune" -ForegroundColor Green
    Remove-Variable -Name "graphAccessToken" -Scope Global -ErrorAction "SilentlyContinue"
    Get-GraphAccessToken -clientID $graphDetails.AppFactoryClientClientID -tenantID $graphDetails.AppFactoryClientTenantID  -clientSecret $graphDetails.AppFactoryClientAppRegSecret | Out-Null
    $intuneApplications = Get-GraphIntuneApp -type "microsoft.graph.win32LobApp"
    Write-Host "Cleanup applications that might of failed but did not cleanup in prior step" -ForegroundColor Green
    foreach ($app in $($intuneApplications | Where-Object { $_.UploadState -eq 0 })) {
      Remove-GraphIntuneApp -applicationid $app.id
    }
    Write-Host "Adding application assignments" -ForegroundColor Green
    Add-AppFactoryClientAppAssignments -applicationList $applicationList -intuneapplications $intuneApplications -LogLevel $LogLevel
    Write-Host "Copying application assignments" -ForegroundColor Green
    Copy-AppFactoryClientAppAssignments -applicationList $applicationList -intuneapplications $intuneApplications -LogLevel $LogLevel
    Write-Host "Removing previous applications if applicable" -ForegroundColor Green
    Remove-AppFactoryClientApp -applicationList $applicationList -intuneapplications $intuneApplications -LogLevel $LogLevel
    Write-Host "Adding app to ESP if configured" -ForegroundColor Green
    Add-AppFactoryClientESPAssignment -applicationList $applicationList -intuneapplications $intuneApplications -LogLevel $LogLevel
    Write-Host "Completed $($applicationList.count) of the original $($originalCount)" -ForegroundColor Green
    foreach ($application in $applicationList) {
      Write-Host "Application Published: $($application.IntuneAppName)" -ForegroundColor Yellow
    }
    if ($applicationList.count -eq $originalCount) {
      return
    }
    Write-Host "==========================================================================================" -ForegroundColor Red
    Write-Host "Some applications failed to import into intune. Waiting 5 minutes to run the process again" -ForegroundColor Red
    Write-Host "==========================================================================================" -ForegroundColor Red
    Start-Sleep -Seconds 300
    Remove-Variable -Name "graphAccessToken" -Scope Global -ErrorAction "SilentlyContinue"
  }  
}
#EndRegion '.\Public\Start-AppFactoryClientProcess.ps1' 84