AppManiProgramManager.psm1


# Version: 1.9.2 Date: 221122 Last Update by: rod@appmani.com
# * Improved error handling on Get-LatestVersion/DownloadLink/Installer
# Version: 1.9.1 Date: 221117 Last Update by: rod@appmani.com
# + Added security measures to Remove-InstallerFolder to prevent deleting C:\ uninstentionally
# * Improved error handling in Confirm-Update and Get-LatestVersion
# Version: 1.9.0 Date: 221113 Last Updated by: rod@appmani.com
# + Add function Get-LatestVersion, Confirm-LogFolder, Write-Log, Disable-IEFirstRunCustomization
# / Replaced usage of Write-Host functions to Write-Log
# * Downloading installers now able to supply download file name independently
# / Get-InstalledProgram now uses a regex so you can match specific programs
# / Confirm-Program/ServiceInstallation changed retry times to 3 from 30
# / Changed Compare-Versions to Confirm-Update where this function utilizes the Get-LatestVersion function to determine if program is due for an update
# Version: 1.8.0 Date: 221011 Last Updated by: rod@appmani.com
# + Added function Set-AgentRefresh
# Version: 1.7.0 Date: 221011 Last Updated by: rod@appmani.com
# + Added function Confirm-InstallerValidity
# Version: 1.6.0 Date: 221009 Last Updated by: rod@appmani.com
# + Added function Get-DownloadLink
# Version: 1.5.0 Date: 220916 Last Updated by: rod@appmani.com
# + Added function Invoke-ModuleForUpdate
# Version: 1.4.1 Date: 220817 Last Updated by: rod@appmani.com
# / Fixed Get-ProgramArchitecture's output from x32 to x86
# Version: 1.4.0 Date: 220817 Last Updated by: rod@appmani.com
# / Changed Get-InstalledProgram/Service parameter 'Program' to 'ProgramName'
# / Get-InstalledProgram's use of wildcards will only be used depending on function call's parameters
# + Added new functions Set-RegistryItem, Get-ProgramArchitecture
# * Improved error handling responses
# 1.3.2 ! Fixed a syntax error
# 1.3.1 - Removed some lines for debugging
# 1.3.0 * Set ProgressPreference to SilentlyContinue to improve download time
# + Now displays current and latest available version of program
# 1.2.2 + Added functions Add-InstallerFolder and Remove-InstallerFolder in functions to export in module manifest
# 1.2.1 - Removed uneeded files in package
# 1.2.0 + Added Add-InstallerFolder function so all installer related files will go to a single folder
# / Changed Remove-Installer function to Remove-InstallerFolder
# 1.1.2 / Changed version just to test updates
# 1.1.1 / Changed author to Appmani
# 1.1.0 + Added Confirm-ServiceInstallation function
# 1.0.0 + First upload

# This function is to mitigate the Invoke-WebRequest error where it won't run because IE First Run Customization hasn't been done yet. Using the switch parameter UseBasicParsing would work for regular web requests, but not for Downloads
Function Test-WebRequest {
    Param (
        $URI
    )

    # Loop until system is able to successfully invoke a web request
    while ($null -eq $webRequest) {
        try {
            $webRequest = Invoke-WebRequest -Uri $URI
        }
        # Catches the exception where IE first run customization has not been done yet
        catch [System.NotSupportedException] {
            Write-Host "Disabling IE First RunCustomization..." -NoNewline
            try {
                Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Internet Explorer\Main" -Name "DisableFirstRunCustomize" -Value 2
            }
            catch {
                Write-Log -LogType ERROR -Message "Failed to disable IE First RunCustomization: $($Error[0])"
                return $null
            }
        }

        # catches other exceptions
        catch {
            Write-Log -LogType ERROR -Message "Failed to execute test webrequest: $($Error[0])"
            return $null
        }
    }
    return $webrequest
}
   
# Downloads installer
Function Get-Installer {
    Param (
        $DownloadLink,
        $SavePath
    )
    $ProgressPreference = 'SilentlyContinue'

    # Tests if save path is existing
    if (Test-Path $savePath) {

        # If a preferred filename is not provided, the test after the last '/' of the download link will serve as the filename of the downloaded file
        # if ($null -eq $FileName) {
        # $filename = $DownloadLink.Substring($DownloadLink.LastIndexOf("/") + 1)
        # }
        # $SavePath = $SavePath + '\' + $fileName
        
        $attempts = 1
        $maxAttempts = 3
        $null = $fileName

        # Downloads the file
        while ($attempts -le $maxAttempts) {
            Write-Log -LogType INFO -Message "Attempts: $attempts" 
            # Downloads the file
            try {
                $download = Invoke-WebRequest -Uri $downloadLink
            }
            catch [System.NotSupportedException] {
                Disable-IEFirstRunCustomization
            }
            catch {
                Write-Log -LogType ERROR -Message "Unable to download installer: $($Error[0])"
                return $null
            }      
            
            # Tries to get filename from Content-Disposition header
            if ($download.Headers["Content-Disposition"]) {
                try {
                    $content = [System.Net.Mime.ContentDisposition]::new($download.Headers["Content-Disposition"])
                    $fileName = $content.FileName
                }
                catch {
                }

            }
            
            # If not, get capture filename from download link
            if (!$fileName) {
                $matches = @()
                # uses GetFileName, decodes any HTTP encoding, removes the character '?' and preceeding characters, and matches it with a filename regex
                Add-Type -AssemblyName System.Web
                ([System.Web.HTTPUtility]::UrlDecode([System.IO.Path]::GetFileName($downloadLink)) -replace '\?.*$') -match '.+\..+$' | Out-Null
                if ($matches[0]) {
                    $fileName = $matches[0]
                }
                # If that still doesn't work use fallback filename
                else {
                    $fileName = "$global:programNameNoSpace.exe"
                }
            
            }

            #$fileName = "$global:programNameNoSpace.exe"
        
            # Actually saves the file to disk
            $installerPath = $SavePath + '\' + $fileName
            #Write-Host $SavePath
            $f = [IO.File]::OpenWrite($installerPath); 
            try { 
                $download.RawContentStream.WriteTo($f); 
            }
            finally { 
                $f.Dispose(); 
            }

            if (Test-Path $installerPath) {
                return $installerPath
            }

            $attempts++
        }

    }
    else {
        Write-Log -LogType ERROR -Message "Download path $SavePath not existing. Please specify a valid path."
     
    }   return $null
    
}

# Creates folder for storing installation files e.g. msi, exe, config files, etc
Function Add-InstallerFolder {
    Param (
        $Path
    )
    If (Test-Path -Path $Path) {
        try {
            Remove-Item -Path $Path -Force -Recurse #-ErrorAction Stop
        }
        catch {
            Write-Log -LogType ERROR -Message "Failed to delete installer folder and its contents: $($Error[0])"
            return $null
        }
    }

    try {
        $installerFolder = New-Item -Path $Path -ItemType Directory
        return $installerFolder
    }
    catch {
        Write-Log -LogType ERROR -Message "Failed to create installer folder: $($Error[0])"
        return $null
    }
}


#Deletes installer
Function Remove-InstallerFolder {
    Param (
        $Path,
        $CleanupDelay
    )

    $minimumPathLength = 4
    if ($Path.Length -lt $minimumPathLength) {
        Write-Log -LogType ERROR "Invalid path. As a security measure, this function needs a path with a minimum length of $minimumPathLength to proceed."
        return $null
    }

    # Cleans up installer folders
    Write-Log -LogType INFO -Message "Clean up will be performed in $cleanupDelay seconds."
    Start-Sleep -Seconds $CleanupDelay
    
    Write-Log -LogType INFO -Message "Cleaning up..." -NoNewline
    if ((Get-Location).Path -eq $Path) {
        Set-Location ..
    }
    # Removes a file
    try {
        Remove-Item -Path $Path -Recurse -Force
    }
    catch {
        Write-Log -LogType ERROR -Message "Failed to delete installer folder: $($Error[0])"
        return $null
    }

    Write-Log -LogType INFO -Message "Removed folder and contents of $Path." 
}

# Checks the registry for entries of the installed program and returns information about it
Function Get-InstalledProgram {
    Param (
        $RegistryDisplayName
    )
    $Apps = @()
    $Apps += Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" # 32 Bit
    $Apps += Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"             # 64 Bit
    $installedProgram = $Apps | Where-Object DisplayName -match $RegistryDisplayName

    if ($installedProgram) {
        return $installedProgram[0]
    }
}

# Checks the registry for entries of the isntalled service and returns information about it
Function Get-InstalledService {
    Param (
        $ServiceDisplayName
    )
    $RegistryPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceDisplayName"

    $installedService = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue

    return $installedService

}


#Installs program using a one-liner msiexec or calls the installer executable with additional arguments
Function Install-Program {
    Param (
        $location,
        $installCommand
    )

    if (Test-Path $location) {
        Set-Location $location
    }
    else {
        Write-Log -LogType ERROR -Message "Unable to change location to $location."
    }

    Write-Log -LogType INFO -Message "Executing command $installCommand"

    try {
        cmd /c $installCommand
    }
    catch {
        Write-Log -LogType ERROR -Message "Unable to install: $($Error[0])"
        return 1
    }

    Write-Log -LogType INFO -Message "Execution completed with exit code $LASTEXITCODE"
    return $LASTEXITCODE
}

Function Confirm-ProgramInstallation {
    Param (
        $RegistryDisplayName
    )
    # Loops X number of times to check registry keys for the program
    $tries = 0
    while ($tries -le 3) {
        $tries++
        Write-Log -LogType INFO -Message "Verifying installation. Tries: $tries"

        $installedProgram = Get-InstalledProgram -RegistryDisplayName $RegistryDisplayName
        
        if ($null -ne $installedProgram) {
            return $installedProgram
        }

        Start-Sleep -s 15
    }
    Write-Log -LogType ERROR -Message "Script has reached the maximum number of retries on installation verification. Please investigate for issues."
    return $null
}

Function Confirm-ServiceInstallation {
    Param (
        $ServiceDisplayName
    )
    # Loops X number of times to check registry keys for the service
    $tries = 0
    while ($tries -le 3) {
        $tries++
        Write-Log -LogType INFO -Message "Verifying installation. Tries: $tries"

        $installedService = Get-InstalledService -ServiceDisplayName $ServiceDisplayName
        
        if ($null -ne $installedService) {
            return $installedService
        }

        Start-Sleep -s 15
    }
    Write-Log -LogType ERROR -Message "Script has reached the maximum number of retries on installation verification. Please investigate for issues."
    return $null
}

# Compares current version of a program from the registry and what's on the download link. There are programs that won't have registry entries and programs that won't have their versions on the download link, so please check first before using
Function Confirm-Update {
    Param (
        $ProgramName,
        $InstalledVersion,
        $VersionMatchRegex
    )

    # Sets a default regex if it's blank or null
    if (!$VersionMatchRegex) { $VersionMatchRegex = '.*' }

    # Removes whitespaces for programs who has a space on its Display Version for some reason like CutePDF: ' 4.1'
    $InstalledVersion = $($InstalledVersion -replace '\s', '')
    
    # Gets latest version available
    $latestVersion = $(Get-LatestVersionNumber -ProgramName $ProgramName).VersionNumber
    if (!$latestVersion) { return $null }

    Write-Log -LogType INFO -Message "Latest version available: $latestVersion"

    # Matches versions to a regex. This is for installed vs scraped latest versions that do not follow the same format e.g. 1.2.3456(registry display version) vs 1.2(scraped from website)
    if (!($InstalledVersion -match $VersionMatchRegex)) { 
        Write-Log -LogType ERROR -Message "Installed version doesn't match version match regex."
        return $false 
    }
    else { $InstalledVersion = $matches[0] }

    if (!($latestVersion -match $VersionMatchRegex)) { 
        Write-Log -LogType ERROR -Message "Latest version doesn't match version match regex."
        return $false 
    }
    else { $latestVersion = $matches[0] }

    return [version]$InstalledVersion -lt [version]$latestVersion
}

Function Set-RegistryItem {
    Param (
        $RegistryPath,
        $Name,
        $Value,
        $PropertyType
    )
    # Create the key if it does not exist
    If (-NOT (Test-Path $RegistryPath)) {
        try {
            New-Item -Path $RegistryPath -Force -ErrorAction Stop #| Out-Null
            Write-Log -LogType INFO -Message "Mew registry path $RegistryPath created."
        }
        catch {
            return $null
        }
    }
  
    # Now set the value
    try {
        New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force -ErrorAction Stop
        Write-Log -LogType INFO -Message "Registry item $RegistryPath\$Name set to $Value."
    }
    catch {
        return $null
    }
}

Function Get-ProgramArchitecture {
    Param (
        $Program
    )

    if ($Program.PSParentPath -eq 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall') {
        return "x86"
    }
    elseif ($Program.PSParentPath -eq 'Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall') {
        return "x64"
    }
    else {
        return $null
    }
}

Function Send-Keys {
    Param (
        $ApplicationWindowTitle,
        $Keys
    )

    $wshell = New-Object -ComObject wscript.shell;
    $wshell.AppActivate($ApplicationWindowTitle)
    $wshell.SendKeys($Keys)
}

Function Invoke-ModuleForUpdate {
    Param (
        $ModuleName
    )
    
    if (!(Get-PackageProvider -ListAvailable | Where-Object Name -eq 'Nuget')) {
        Write-Log -LogType INFO -Message "Installing Nuget..."
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Install-PackageProvider -Name Nuget -Force | Out-Null
    }
    Write-Log -LogType INFO -Message "Retrieving installed module..."
    $installedModule = Get-InstalledModule $ModuleName -ErrorAction SilentlyContinue

    # If not install module from PSGallery
    if ($null -eq $installedModule) {
        Write-Log -LogType INFO -Message "$ModuleName module not installed. Please install $ModuleName first."
    }
    # If module is installed check for updates and import
    else {            
        # Gets latest module version available in PSGallery
        $latestModuleVersion = Find-Module $ModuleName -ErrorAction Ignore
        if ($latestModuleVersion) {
                
            # Checks if installed module version needs an update
            if ($latestModuleVersion.Version -ne $installedModule.Version) {
                Write-Log -LogType INFO -Message "Installing new version of $ModuleName..." -NoNewline
                try {
                    Update-Module $ModuleName -Force -ErrorAction Stop
                    Write-Log -LogType INFO -Message "Done!"
                }
                catch {
                    Write-Log -LogType ERROR -Message "The script ran into an issue: $($Error[0])"
                    return $null
                }
            }
            Else {
                Write-Log -LogType INFO -Message "Module $ModuleName is already up to date."
            }
        }

    }
}

Function Get-DownloadLink {
    Param (
        $ProgramName,
        $Architecture
    )

    $Architecture = if ($Architecture) { $Architecture }
    else { 'x64' }

    $attempts = 1
    $maxAttempts = 3

    while ($attempts -le $maxAttempts) {
        Write-Log -LogType INFO -Message "Attempts: $attempts"
        try {
            switch ($ProgramName) {
                '7-zip' {
                        
                    $HomePage = "https://www.7-zip.org/"
            
                    if ($architecture -eq 'x64') {
                        $architecture = '-x64'
                    }
                    else {
                        $architecture = ''
                    }
            
                    $HTML = Invoke-RestMethod 'https://www.7-zip.org/download.html'
                    $Pattern = '<A href=\"(?<link>a/7z\d+{0}\.exe)\">Download</A>' -f $architecture
                    $AllMatches = ([regex]$Pattern).Matches($HTML)
                    $link = ($AllMatches[0].Groups.Where{ $_.Name -like 'link' }).Value
                
                    if ($null -ne $link) {
                        $downloadLink = $HomePage + "$($link)"
                        return $downloadLink
                    }
                    else {
                        Write-Log -LogType ERROR -Message "Version requested is not available."
                        return $null
                    }
                }
            
                'Adobe Acrobat' {
                    $acrobatReleaseNotesURL = 'https://helpx.adobe.com/acrobat/release-note/release-notes-acrobat-reader.html'
                    $versionRegex = '\d{2}\.\d{3}\.\d{5}'
            
                    if ($architecture -eq 'x64') {
                        $downloadLinkFormat = 'https://ardownload2.adobe.com/pub/adobe/acrobat/win/AcrobatDC/{0}/'
                        $fileNameFormat = 'AcroRdrDCx64{0}_en_US.exe'
                    }
                    elseif ($architecture -eq 'x86') {
                        $downloadLinkFormat = 'https://ardownload2.adobe.com/pub/adobe/reader/win/AcrobatDC/{0}/'
                        $fileNameFormat = 'AcroRdrDC{0}_en_US.exe'
                        #$versionOverride = '22.002.20191'
                    }
                    
                    # Gets list of versions from Adobe Acrobat release notes
                    $versionsAvailable = $(invoke-webrequest -uri $acrobatReleaseNotesURL -UseBasicParsing).Links | `
                        Select-Object @{ label = 'version'; expression = { $_.outerHTML -match $versionRegex | Out-Null; return $matches[0] } } | `
                        Where-Object { $null -ne $_.version } | Sort-Object version -Descending
                    
                    # If versionRequested is 'latest' script gets the first available version from $versionsAvailable
                    $latestVersion = $versionsAvailable[0].version
                    
                    #Checks if version requested is available then generates download link and filename
                    if ($versionsAvailable | Where-Object version -eq $latestVersion) {
                        $versionRequested = $latestVersion
                        if ($versionOverride) {
                            Write-Log -LogType INFO -Message "Version override detected: $versionOverride"
                            $versionRequested = $versionOverride
                        }
                        $versionRequested = $versionRequested -replace '[.]', ''
                    
                        $filenameFormat = $filenameFormat -f $versionRequested
                        $downloadLink = $("$downloadLinkFormat" + "$filenameFormat") -f $versionRequested
                        return $downloadLink
                    }
                    else {
                        Write-Log -LogType ERROR -Message "Version requested is not available."
                        return $null
                    }
                }
                'Audacity' {
                    if ($architecture -eq 'x64') {
                        $architecture = '64 bit'
                    }
                    elseif ($architecture -eq 'x86') {
                        $architecture = '32 bit'
                    }
                    
                    $HTML = Invoke-RestMethod 'https://www.audacityteam.org/download/windows/'
                    $Pattern = '<a href=\"(?<link>.*)\">Audacity .+? {0} installer</a>' -f $architecture
                    $AllMatches = ([regex]$Pattern).Matches($HTML)
                    $downloadLink = ($AllMatches[0].Groups.Where{ $_.Name -like 'link' }).Value
                    
                    return $downloadLink
                }
                'Bitwarden' {
                    $originalLink = 'https://vault.bitwarden.com/download/?app=desktop&platform=windows'
                    $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                    return $downloadLink
                }
                'Citrix Workspace' {
                    $downloadLink = ((Invoke-WebRequest -URI 'https://www.citrix.com/downloads/workspace-app/windows/workspace-app-for-windows-latest.html').Links | Where-Object { ($_.outerText -like 'Download *') -and ($_.rel -like '*CitrixWorkspaceApp.exe*') }).rel[0]
                    $downloadLink = "https:" + $downloadLink
                    return $downloadLink
                }
                'CutePDF Writer' {
                    $downloadLink = 'https://www.cutepdf.com/download/CuteWriter.exe'
                    return $downloadLink
                }
                'Digisign' {
                    $HTML = Invoke-RestMethod 'https://www.linz.govt.nz/guidance/landonline-support/legacy-landonline-support/software-downloads-and-installation/software-downloads'
                    $Pattern = '<a class=\"button\" href=\"(?<link>.*)\">Digisign.+?</a>'
                    $AllMatches = ([regex]$Pattern).Matches($HTML)
                    $downloadLink = ($AllMatches[0].Groups.Where{ $_.Name -like 'link' }).Value
                    return $downloadLink
                }
                'Dropbox' {
                    $originalLink = 'https://www.dropbox.com/download?full=1&plat=win'
                    $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                    return $downloadLink
                }
                'Microsoft Edge' {
                    $originalLink = 'https://go.microsoft.com/fwlink/?linkid=2109047&Channel=Stable&language=en&consent=1'
                    $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                    return $downloadLink
                }
                'Filezilla' {
            
                    if ($architecture -eq 'x64') {
                        $architecture = 'win64'
                    }
                    elseif ($architecture -eq 'x86') {
                        $architecture = 'win32'
                    }
                        
                    $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
                    $session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
                    $HTML = Invoke-RestMethod -UseBasicParsing -Uri "https://filezilla-project.org/download.php?show_all=1" -WebSession $session                    
                    $Pattern = '<a href=\"(?<link>.*)\" rel="nofollow">FileZilla_.+?_{0}-setup.exe</a>' -f $architecture
                    $AllMatches = ([regex]$Pattern).Matches($HTML)
                    $downloadLink = ($AllMatches[0].Groups.Where{ $_.Name -like 'link' }).Value
                    
                    return $downloadLink
                }
                'Foxit PDF Reader' {
                    $originalLink = 'https://www.foxit.com/downloads/latest.html?product=Foxit-Reader&platform=Windows&version=&package_type=&language=English&distID='
                    $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                    return $downloadLink
                }
                'Google Drive' {
                    $downloadLink = 'https://dl.google.com/drive-file-stream/GoogleDriveSetup.exe'
                    return $downloadLink        
                }
                'GPL Ghostscript' {
                    if ($Architecture -eq 'x64') {
                        $Architecture = 'w64'
                    }
                    else {
                        $Architecture = 'w32'
                    }
                        
                    $link = 'https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/latest'
                    $latestLink = (Invoke-WebRequest -Uri $link -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                    $latestLink -match 'gs(.+?)$' | Out-Null
                    $downloadLink = $latestLink.replace('tag', 'download') + "/gs$($matches[1])$Architecture.exe"
                    return $downloadLink
                }
                'Google Chrome' {
                    $downloadLink = 'http://dl.google.com/edgedl/chrome/install/GoogleChromeStandaloneEnterprise64.msi'
                    return $downloadLink
                }
                'HP Support Assistant' {
                    $downloadLink = 'https://ftp.ext.hp.com/pub/softpaq/sp141501-142000/sp141886.exe'
                    return $downloadLink
                }
                'IrfanView' {
                    $test = (Invoke-WebRequest -Uri "https://www.fosshub.com/IrfanView.html" -UseBasicParsing).content 
                    $data = ($test | Select-String -Pattern '(?<=\s=).*').matches.value | ConvertFrom-Json
            
                    if ($Architecture -eq 'x86') {
                        $Architecture = ''
                    }
                
                    try { 
                        $Url = 'https://api.fosshub.com/download' 
                        $Params = @{ 
                            Uri             = $Url 
                            Body            = @{ 
                                projectId  = "$($data.projectId)" 
                                releaseId  = "$($data.pool.f.r | Select -Unique)" 
                                projectUri = 'IrfanView.html' 
                                fileName   = $((($data).pool.f | Where-Object { $_.n -match ('iview(\d+)_?{0}_setup\.exe' -f $architecture) }))[0].n
                                source     = "$($data.pool.c)" 
                            }
                            Headers         = @{
                                'User-Agent' = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome
                            }
                            Method          = 'POST'
                            UseBasicParsing = $true
                        }
                        $info = (Invoke-WebRequest @Params).Content | ConvertFrom-Json
                        $ErrorType = $Response.error
                        if ($ErrorType -ne $Null) {
                            throw "ERROR RETURNED $ErrorType"
                            return $Null
                        }
                        return ($info.data)[0].url
                    }
                    catch {
                        Write-Error $_
                    }
                }
                'Jabra Direct' {
                    $downloadLink = 'https://jabraxpressonlineprdstor.blob.core.windows.net/jdo/JabraDirectSetup.exe'
                    return $downloadLink
                }
                'Java 8' {
    
            
                    if ($architecture -eq 'x64') {
                        $architecture = '\(64-bit\)'
                    }
                    else {
                        $architecture = 'Offline'
                    }
            
                    $URL = "https://www.java.com/en/download/manual.jsp"
                    $global:ie = New-Object -com "InternetExplorer.Application"
                    $global:ie.visible = $false
                    $global:ie.Navigate($URL)
            
                    DO { Start-Sleep -s 1 }UNTIL(!($global:ie.Busy))
                    Start-Sleep 5
                    if ($global:ie.Document.body.innerHTML) {
                        $HTML = $global:ie.Document.body.innerHTML.ToString()
                    }
                     else { return $null }
                    
                    $Pattern = '<a title=\"Download Java software for Windows {0}\" href=\"(?<link>.*)\"><img' -f $architecture
                    $AllMatches = ([regex]$Pattern).Matches($HTML)
                    $downloadLink = ($AllMatches[0].Groups.Where{ $_.Name -like 'link' }).Value
    
                    #return $downloadLink
                }
                'LOLComponents' {
                    $HTML = Invoke-RestMethod 'https://www.linz.govt.nz/guidance/landonline-support/legacy-landonline-support/software-downloads-and-installation/software-downloads'
                    $Pattern = '<a class="button" href=\"(?<link>.*)\">Landonline Client Components \(ZIP .+?\)</a>'
                    $AllMatches = ([regex]$Pattern).Matches($HTML)
                    $downloadLink = ($AllMatches[0].Groups.Where{ $_.Name -like 'link' }).Value
                    return $downloadLink
                }
                'Microsoft 365' {
                    $downloadLink = 'https://download.microsoft.com/download/2/7/A/27AF1BE6-DD20-4CB4-B154-EBAB8A7D4A7E/officedeploymenttool_15128-20224.exe'                    
                    return $downloadLink
                }
                'Mozilla Firefox' {
                    if ($architecture -eq 'x64') {
                        $originalLink = 'https://download.mozilla.org/?product=firefox-latest-ssl&os=win64&lang=en-US'
                    }
                    elseif ($architecture -eq 'x86') {
                        $originalLink = 'https://download.mozilla.org/?product=firefox-latest-ssl&os=win&lang=en-US'
                    }
                    
                    $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                    return $downloadLink
                }
                'Net Monitor for Employees Agent' {
                    $downloadLink = 'https://networklookout.com/dwn/nmemplpro_agent.msi'                    
                    return $downloadLink
                }
                'Notepad++' {
                        
                    $NPlusPlusWebsite = "https://notepad-plus-plus.org"
                    $DownloadPage = $NPlusPlusWebsite + "/downloads"
                    $filter = '*Installer.exe'
                    if ($architecture -eq 'x64') {
                        $filter = '*Installer.x64.exe'
                    }
                    
                    try {
                        $currentVersion = $($(Invoke-WebRequest -Uri $DownloadPage).Links | Where-Object innerText -like 'Current Version*').href
                    }
                    catch {
                        Write-Log -LogType ERROR -Message "The script ran into an issue: $($Error[0])"
                        return $null
                    }
                    
                    if ($null -ne $currentVersion) {
                        $NPlusPlusCurrentVersionDownloadPage = $NPlusPlusWebsite + $currentVersion
                        
                        try {
                            $downloadLink = $(Invoke-WebRequest $NPlusPlusCurrentVersionDownloadPage).Links.href -like $filter | Select-Object -First 1
                            return $downloadLink
                        }
                        catch {
                            Write-Log -LogType ERROR -Message "The script ran into an issue: $($Error[0])"
                            return $null
                        }
                    }
                    else {
                        Write-Log -LogType ERROR -Message "Unable to find current version."
                        return $null
                    }
                }
                'PDFCreator' {
                    $originalLink = 'https://download.pdfforge.org/download/pdfcreator/PDFCreator-stable?download'
                    $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                    return $downloadLink
                }
                'Putty' {
                    $PuttyDownloadPage = "https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html"
            
                    if ($Architecture -eq 'x64') { $Architecture = 'w64' }
                    elseif ($Architecture -eq 'x86') { $Architecture = 'w32' }
            
                    try {
                        $links = (Invoke-WebRequest $PuttyDownloadPage).Links.href 
                    }
                    catch {
                        Write-Log -LogType ERROR -Message "The script ran into an issue: $($Error[0])"
                        return $null
                    }
            
                    if ($links) {
                        $downloadLink = $links | Where-Object { ($_ -match "$Architecture/(.+?)-installer\.msi$") }
                    }
                    else {
                        Write-Log -LogType ERROR -Message "No links found."
                        return $null
                    }
            
                    if ($downloadLink) {
                        return $downloadLink
                    }
                    else {
                        Write-Log -LogType ERROR -Message "Unable to capture download link."
                        return $null
                    }
                }
                'Python' {
                    $downloadLink = $(Invoke-WebRequest -Uri 'https://www.python.org/downloads/').Links.href | Where-Object { $_ -like '*.exe' }
                    if ($architecture -eq 'x86') {
                        $downloadLink = $downloadLink -replace "-amd64", ""
                    }
                    return $downloadLink
                }
                'Sysmon64' {
                    $downloadLink = 'https://download.sysinternals.com/files/Sysmon.zip'
                    return $downloadLink
                }
                'Microsoft Teams' {
                    $originalLink = 'https://teams.microsoft.com/downloads/desktopcontextualinstaller?env=prod&intent=work&plat=windows&download=true'
                    $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                    return $downloadLink
                }
                'TreeSize Free' {
                    $downloadLink = 'https://downloads.jam-software.de/treesize_free/TreeSizeFreeSetup.exe'
                    return $downloadLink
                }
                'UniPrint' {
                    $regex = "UniPrintClientMSI_\d+_$architecture.zip$"
                    $downloadLink = (Invoke-WebRequest -Uri 'https://www.uniprint.net/en/uniprint-client/' -UseBasicParsing).Links.href | Where-Object { $_ -match $regex }
                    return $downloadLink
                }
                'VLC' {
                    $VLCDownloadPage = "https://www.videolan.org/vlc/download-windows.html"
                    if ($architecture -eq 'x64') {
                        $filter = '*win64.exe'
                    }
                    elseif ($architecture -eq 'x86') {
                        $filter = '*win32.exe'
                    }
                        
                    try {
                        $versionDownloadPage = $(Invoke-WebRequest -Uri $VLCDownloadPage).Links | Where-Object href -like $filter
                    }
                    catch {
                        Write-Log -LogType ERROR -Message "The script ran into an issue: $($Error[0])"
                        return $null
                    }
                        
                    $versionDownloadPage = "https:" + $versionDownloadPage.href
                    try {
                        $downloadLink = $((Invoke-WebRequest -Uri $versionDownloadPage).Links | Where-Object href -like $filter | Select-Object -First 1).href
                        return $downloadLink
                    }
                    catch {
                        Write-Log -LogType ERROR -Message "The script ran into an issue: $($Error[0])"
                        return $null
                    }
                }
                'Wireshark' {
                    $Release = 'Stable Release'
                    $wiresharkDownloadPage = "https://www.wireshark.org/download.html"
                        
                    $versionRegex = '\((.+?)\)'
            
                    if ($Architecture -eq 'x64') {
                        $Architecture = 'win64'
                    }
                    elseif ($Architecture -eq 'x32') {
                        $Architecture = 'win32'
                    }
            
                    $webRequest = Invoke-WebRequest -Uri $WiresharkDownloadPage
                
                    # Checks webrequest links for the version number e.g. "Stable 3.6.6" then finds the download link for it
                        ($webRequest).Links.innerHTML | Foreach-Object {
                        if ($_ -match "^$release") {
                            $_ -match $versionRegex | Out-Null
                            $version = $matches[1]
                        }
                    }
            
                    $downloadLink = $webRequest.Links.href | Where-Object { $_ -match "$Architecture-$version" }
                    return $downloadLink
                }
                'Zoom' {
                    $downloadLink = 'http://zoom.us/client/latest/ZoomInstallerFull.msi'
                    return $downloadLink
                }
                Default {
                    Write-Log -LogType INFO -Message "No matching function to retrieve download link for $programName"
                    return $null
                }
            }
        }
        catch [System.NotSupportedException] {
            Disable-IEFirstRunCustomization
        }
        catch {
            Write-Log -LogType ERROR "Unable to retrieve latest version number for $ProgramName. $($Error[0])"
        }

        if ($downloadLink) {
            return $downloadLink
        }

        $attempts++
        #Start-Sleep 5
    }

    Write-Log -LogType ERROR -Message "The maximum number of attempts to retrieve the download link has been reached."
    return $null
}

Function Get-LatestVersionNumber {
    Param (
        $ProgramName
    )

    try {
        switch ($ProgramName) {
            '7-zip' {            
                $HTML = Invoke-RestMethod 'https://www.7-zip.org/download.html' -ErrorAction Stop
                $Pattern = '<B>Download 7-Zip (?<version>.*) \((.+?)\)</B>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Adobe Acrobat' {
                $HTML = Invoke-RestMethod 'https://helpx.adobe.com/acrobat/release-note/release-notes-acrobat-reader.html'  -ErrorAction Stop
                $Pattern = '<td><a.*>.*\((?<version>.*)\)</a><br />'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Audacity' {
                $HTML = Invoke-RestMethod 'https://www.audacityteam.org/download/windows/'  -ErrorAction Stop
                $Pattern = '<h2>Current Version: (?<version>.*)</h2>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Bitwarden' {
                $originalLink = 'https://vault.bitwarden.com/download/?app=desktop&platform=windows'
                $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                $Pattern = '/Bitwarden-Installer-(?<version>.*)\.exe'
                $AllMatches = ([regex]$Pattern).Matches($downloadLink)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'CutePDF Writer' {
                $HTML = Invoke-RestMethod 'https://www.cutepdf.com/products/CutePDF/writer.asp' -ErrorAction Stop
                $Pattern = 'Ver\. (?<version>.*); .+? MB\)'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Citrix Workspace' {
                $HTML = Invoke-RestMethod 'https://www.citrix.com/downloads/workspace-app/windows/workspace-app-for-windows-latest.html' -ErrorAction Stop
                $Pattern = '<p><b>Version:</b>&nbsp;(?<version>[\d\.]+).*</p>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Digisign' {
                $HTML = Invoke-RestMethod 'https://www.linz.govt.nz/guidance/landonline-support/legacy-landonline-support/software-downloads-and-installation/software-downloads' -ErrorAction Stop
                $Pattern = '<p><a class=\"button\" href=\".+?\">Digisign.+? \(EXE .+?MB v(?<version>[\d\.]+) .+?\)</a></p>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Dropbox' {
                $HTML = Invoke-RestMethod 'https://www.dropboxforum.com/t5/forums/filteredbylabelpage/board-id/101003016/label-name/stable%20build' -ErrorAction Stop
                $Pattern = '<h3><a href=".*">Stable Build (?<version>.*)</a></h3>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Microsoft Edge' {
                $HTML = Invoke-RestMethod 'https://docs.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel' -ErrorAction Stop
                $Pattern = '<h2 id=".*">Version (?<version>.*):.*</h2>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'FileZilla' {
                $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
                $session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
                $HTML = Invoke-RestMethod -UseBasicParsing -Uri "https://filezilla-project.org/download.php?show_all=1" -WebSession $session -ErrorAction Stop
                $Pattern = '<p>The latest stable version of FileZilla Client is (?<version>.*)</p>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Foxit PDF Reader' {
                $HTML = Invoke-RestMethod 'https://www.foxit.com/pdf-reader/version-history.html' -ErrorAction Stop
                $Pattern = '<p><h3>Version (?<version>.*)</h3></p>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'GPL Ghostscript' {
                $HTML = Invoke-RestMethod 'https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/latest' -ErrorAction Stop
                $Pattern = '<h1 data-view-component="true" class="d-inline mr-3">Ghostscript/GhostPDL (?<version>.*)</h1>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Google Drive' {
                $HTML = Invoke-RestMethod 'https://support.google.com/a/answer/7577057?hl=en' -ErrorAction Stop
                $Pattern = '<p><em><strong>Windows( and macOS)?:</strong>&nbsp;Version (?<version>.*)</em></p>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Google Chrome' {
                $HTML = Invoke-RestMethod 'https://chromereleases.googleblog.com/search/label/Desktop%20Update' -ErrorAction Stop
                $Pattern = 'The Stable channel has been updated to (?<version>[\d\.]+) for'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'HP Support Assistant' {
                $HTML = Invoke-RestMethod 'https://support.hp.com/us-en/help/hp-support-assistant' -ErrorAction Stop
                $Pattern = '<span class="bannerVersion">Version <span class="ver">(?<version>.*)</span>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'IrfanView' {
                $HTML = Invoke-RestMethod 'https://www.irfanview.com/' -ErrorAction Stop
                $Pattern = '<h2>Get IrfanView \(<strong>version (?<version>.*)</strong>\)</h2>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Jabra Direct' {
                $HTML = Invoke-RestMethod 'https://www.jabra.co.nz/Support/release-notes/release-note-jabra-direct' -ErrorAction Stop
                $Pattern = '<p><strong>Release version:</strong> (?<version>.*)<br>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Java' {
                $latestVersion = $null
                $maxAttempts = 5
            
                $attempts = 0
                while (($null -eq $latestVersion) -and ($attempts -lt $maxAttempts)) {
            
                    $URL = "https://www.java.com/en/download/manual.jsp"
                    $global:ie = New-Object -com "InternetExplorer.Application"
                    $global:ie.visible = $false
                    $global:ie.Navigate($URL)
            
                    DO { Start-Sleep -s 1 }UNTIL(!($global:ie.Busy))
                    Start-Sleep 5
                    $HTML = $global:ie.Document.body.innerHTML.ToString()
                    $Pattern = '<h4 class="sub">Recommended (?<version>.*)</h4>'
                    $AllMatches = ([regex]$Pattern).Matches($HTML)
                    $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
            
                    $attempts++
                }
                break
            }
            'Mozilla Firefox' {
                $Pattern = 'releases/(?<version>[\d\.]+)/'
                $originalLink = 'https://download.mozilla.org/?product=firefox-latest-ssl&os=win64&lang=en-US'
                $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                $AllMatches = ([regex]$Pattern).Matches($downloadLink)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                $break
            }
            'Network Lookout for Employees Pro' {
                $HTML = Invoke-RestMethod 'https://networklookout.com/' -ErrorAction Stop
                $Pattern = '<p>ver. (?<version>[\d\.]+)'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Notepad++' {
                $HTML = Invoke-RestMethod 'https://notepad-plus-plus.org/downloads/' -ErrorAction Stop
                $Pattern = '<a href=".*"><strong>Current Version (?<version>.*)</strong></a>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'PDFCreator' {
                $HTML = (Invoke-WebRequest 'https://docs.pdfforge.org/pdfcreator/en/pdfcreator/introduction/whats-new/' -UseBasicParsing -ErrorAction Stop).Content
                $Pattern = '<h2>PDFCreator (?<version>.*)<a'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Putty' {
                $HTML = Invoke-RestMethod 'https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html' -ErrorAction Stop
                $Pattern = '<TITLE>Download PuTTY: latest release \((?<version>.*)\)</TITLE>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Python' {
                $HTML = Invoke-RestMethod 'https://www.python.org/downloads/windows/' -ErrorAction Stop
                $Pattern = '<li><a href="/downloads/release/python-.+?/">Latest Python 3 Release - Python (?<version>.*)</a></li>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Sysmon' {
                $HTML = Invoke-RestMethod 'https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon' -ErrorAction Stop
                $Pattern = '<h1 id="sysmon-.*">Sysmon v(?<version>.*)</h1>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Microsoft Teams' {
                $HTML = Invoke-RestMethod 'https://docs.microsoft.com/en-us/officeupdates/teams-app-versioning' -ErrorAction Stop
                $Pattern = '<h3 id="windows-public-cloud-version-history">Windows \(Public Cloud\) version history</h3>(\n.*){14}\n<td style="text-align: left;">(?<version>.*)</td>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'TreeSize Free' {
                $HTML = Invoke-RestMethod 'https://www.jam-software.com/treesize_free/changes.shtml' -ErrorAction Stop
                $Pattern = '<h3 class="collapsed-item__ttl">Version (?<version>.*)</h3>'
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'UniPrint' {
                $HTML = Invoke-WebRequest 'https://www.uniprint.net/en/uniprint-client/' -ErrorAction Stop
                $Pattern = "<p>UniPrint Client .*; (?<version>.*) Autodetect and Install</p>"
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'VLC' {
                $HTML = Invoke-RestMethod 'https://www.videolan.org/vlc/download-windows.html' -ErrorAction Stop
                $Pattern = "<span id='downloadVersion'>\n\s*(?<version>[\d\.]+)</span>"
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Wireshark' {
                $HTML = Invoke-RestMethod 'https://www.wireshark.org/download.html' -ErrorAction Stop
                $Pattern = "<a.*>Stable Release \((?<version>.*)\)</a>"
                $AllMatches = ([regex]$Pattern).Matches($HTML)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            'Zoom' {
                $Pattern = 'prod/(?<version>.*)/ZoomInstallerFull\.msi'
                $originalLink = 'https://zoom.us/client/latest/ZoomInstallerFull.msi'
                $downloadLink = (Invoke-WebRequest -Uri $originalLink  -MaximumRedirection 0 -ErrorAction Ignore).Headers.Location
                $AllMatches = ([regex]$Pattern).Matches($downloadLink)
                $latestVersion = ($AllMatches[0].Groups.Where{ $_.Name -like 'version' }).Value
                break
            }
            default {
                $latestVersion = $null
            }
        }
    
    }
    catch [System.NotSupportedException] {
        Disable-IEFirstRunCustomization
    }
    catch {
        Write-Log -LogType ERROR "Unable to retrieve latest version number for $ProgramName. $($Error[0])"
        return $null
    }

    $obj = [PSCustomObject]@{
        ProgramName   = $ProgramName
        VersionNumber = $latestVersion
    }

    return $obj

}

Function Confirm-InstallerValidity {
    Param(
        $FilePath
    )

    $varChain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain
    try {
        $verification = $varChain.Build((Get-AuthenticodeSignature -FilePath "$FilePath").SignerCertificate)
        return $verification
    }
    catch [System.Management.Automation.MethodInvocationException] {
        $err = ( "'$FilePath' did not contain a valid digital certificate. " +
            "Something may have corrupted/modified the file during the download process. " +
            "Suggest trying again, contact support@appmani.com if it fails >2 times")
        Write-Log -LogType ERROR -Message $err
        return $null
    }
}

Function Set-AgentRefresh {
    Param (
        $NewRefreshCheckValue
    )

    $auditRefreshRegistryPath = 'HKLM:\SOFTWARE\NZCS\ServiceCAT'
    $auditRefreshRegistryItemName = 'RefreshCheck'

    # Gets refresh check value
    $CurrentRefreshCheckValue = Get-ItemProperty -Path $auditRefreshRegistryPath -Name $auditRefreshRegistryItemName #| Out-Null# -ErrorAction Stop

    # Checks if new and current RefreshCheck values are different
    if ($NewRefreshCheckValue -ne $CurrentRefreshCheckValue.RefreshCheck) {
        # Sets new RefreshCheck value if they are different
        Set-RegistryItem -RegistryPath $auditRefreshRegistryPath -Name $auditRefreshRegistryItemName -Value $NewRefreshCheckValue | Out-Null
    
    }
}

Function Confirm-LogFolder {

    $parentFolderPath = 'C:\Windows\Temp\AppManiProgramManagerLogs\'
    $logFolderPath = $parentFolderPath + $global:scriptName

    $logFolder = Test-Path $logFolderPath

    if (!($logFolder)) {
        try {
            New-Item -Path $logFolderPath -ItemType Directory | Out-Null            
        }
        catch {
            Write-Log -LogType ERROR -Message "Failed to create log folder: $($Error[0])"
            return $false
        }
    }

    Write-Log -LogType INFO -Message "Logs will be saved at $logFolderPath."
    return $true
}

Function Write-Log {
    Param (
        $LogType,
        $Message
    )

    $parentFolderPath = 'C:\Windows\Temp\AppManiProgramManagerLogs\'
    $logFolderPath = $parentFolderPath + $global:scriptName
    $logFileName = "$global:scriptName-" + (Get-Date -Format 'yyMMdd') + '.log'
    $logFilePath = "$logFolderPath\" + $logFileName

    $longDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

    $msg = '{0} {1} {2}: {3}' -f $longDate, $global:scriptName, $LogType, $Message
    Add-Content -Path $logFilePath -Value $msg -ErrorAction Ignore
    Write-Host $msg
}

Function Disable-IEFirstRunCustomization {
    Write-Log -LogType INFO -Message "Unable to execute WebRequest. Disabling IE First RunCustomization..."
    try {
        Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Internet Explorer\Main" -Name "DisableFirstRunCustomize" -Value 2
    }
    catch {
        Write-Log -LogType ERROR -Message "Failed to disable IE First RunCustomization: $($Error[0])"
        return $null
    }
}

Export-ModuleMember -Function 'Test-WebRequest'
Export-ModuleMember -Function 'Get-Installer'
Export-ModuleMember -Function 'Add-InstallerFolder'
Export-ModuleMember -Function 'Remove-InstallerFolder'
Export-ModuleMember -Function 'Get-InstalledProgram'
Export-ModuleMember -Function 'Get-InstalledService'
Export-ModuleMember -Function 'Install-Program'
Export-ModuleMember -Function 'Confirm-ProgramInstallation'
Export-ModuleMember -Function 'Confirm-ServiceInstallation'
Export-ModuleMember -Function 'Confirm-Update'
Export-ModuleMember -Function 'Set-RegistryItem'
Export-ModuleMember -Function 'Get-ProgramArchitecture'
Export-ModuleMember -Function 'Send-Keys'
Export-ModuleMember -Function 'Invoke-ModuleForUpdate'
Export-ModuleMember -Function 'Get-DownloadLink'
Export-ModuleMember -Function 'Get-LatestVersionNumber'
Export-ModuleMember -Function 'Confirm-InstallerValidity'
Export-ModuleMember -Function 'Set-AgentRefresh'
Export-ModuleMember -Function 'Confirm-LogFolder'
Export-ModuleMember -Function 'Write-Log'
Export-ModuleMember -Function 'Disable-IEFirstRunCustomization'