AppManiProgramManager.psm1

# 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-Warning "Failed to disable IE First RunCustomization: $($Error[0])"
                return $null
            }
        }

        # catches other exceptions
        catch {
            Write-Warning "Failed to execute test webrequest: $($Error[0])"
            return $null
        }
    }
    return $webrequest
}
   
# Downloads installer
Function Get-Installer {
    Param (
        $DownloadLink,
        $SavePath,
        $FileName
    )
    $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
        
        # Downloads the file
        try {
            Invoke-WebRequest -Uri $downloadLink -OutFile $savePath
        }
        catch {
            Write-Warning "Unable to download installer: $($Error[0])"
            return $null
        }
        return $SavePath
    }
    else {
        Write-Warning "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-Warning "Failed to delete installer folders and its contents: $($Error[0])"
            return $null
        }
    }

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


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

    # Removes a file
    try {
        Remove-Item -Path $Path -Recurse -Force
    }
    catch {
        Write-Warning "Failed to delete installer folder: $($Error[0])"
        return $null
    }
}

# Checks the registry for entries of the installed program and returns information about it
Function Get-InstalledProgram {
    Param (
        $ProgramName
    )
    $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 -like "$ProgramName"

    return $installedProgram
}

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

    $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 (
        $InstallCommand,
        $Program
    )
    try {
        cmd /c $InstallCommand
    }
    catch {
        Write-Warning "Unable to install $($Program): $($Error[0])"
        return 1
    }
    #Write-Host "Exit code: $LASTEXITCODE"
    return $LASTEXITCODE
}

Function Confirm-ProgramInstallation {
    Param (
        $ProgramName
    )
    # Loops X number of times to check registry keys for the program
    $tries = 0
    while ($tries -le 30) {
        $tries++
        Write-Host "Verifying installation. Tries: $tries"

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

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

Function Confirm-ServiceInstallation {
    Param (
        $ProgramName
    )
    # Loops X number of times to check registry keys for the service
    $tries = 0
    while ($tries -le 30) {
        $tries++
        Write-Host "Verifying installation. Tries: $tries"

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

        Start-Sleep -s 15
    }
    Write-Warning "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 Compare-Versions {
    Param (
        $InstalledProgram,
        $DownloadLink,
        $downloadLinkRegex,
        $regexMatchIndex = 1
    )

    $DownloadLink -match $downloadLinkRegex | Out-Null
    $latestVersion = $matches[$regexMatchIndex] -replace '[.]', ''
    $currentVersion = $($InstalledProgram.DisplayVersion) -replace '[.]',''

    Write-Host "Version installed: $currentVersion"
    Write-Host "Latest version: $latestVersion"

    if ($latestVersion.equals($currentVersion)) {
        return $false
    }
    else {
        return $true
    }
}

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-Host "Successfully created new registry path $RegistryPath."
        }
        catch {
            return $null
        }
    }
  
    # Now set the value
    try {
        New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force -ErrorAction Stop
        Write-Host "Successfully set registry item $RegistryPath\$Name 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-Host "Installing Nuget..."
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Install-PackageProvider -Name Nuget -Force | Out-Null
    }
    Write-Host "Retrieving installed module..."
    $installedModule = Get-InstalledModule $ModuleName -ErrorAction SilentlyContinue

    # If not install module from PSGallery
    if ($null -eq $installedModule) {
        Write-Host "$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-Host "Installing new version of $ModuleName..." -NoNewline
                try {
                    Update-Module $ModuleName -Force -ErrorAction Stop
                    Write-Host "Done!"
                }
                catch {
                    Write-Warning "The script ran into an issue: $($Error[0])"
                    return $null
                }
            }
            Else {
                Write-Host "Module $ModuleName is already up to date."
            }
        }

    }
}

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

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

    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'
            $HTML > C:\Code\AppMani\html.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-Warning "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-Host "Version override detected: $versionOverride"
                    $versionRequested = $versionOverride
                }
                $versionRequested = $versionRequested -replace '[.]', ''
        
                $filenameFormat = $filenameFormat -f $versionRequested
                $downloadLink = $("$downloadLinkFormat" + "$filenameFormat") -f $versionRequested
                return $downloadLink
            }
            else {
                Write-Warning "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/'
            $HTML > C:\Code\AppMani\html.html
            $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
                }
                #Write-Host "Invoke-WebRequest $Params"
                $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 $_
            }
        }
        'Java' {
            $downloadLink = $null
            $maxAttempts = 5

            if ($architecture -eq 'x64') {
                $architecture = '\(64-bit\)'
            }
            else {
                $architecture = 'Offline'
            }

            $attempts = 0
            while (($null -eq $downloadLink) -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))
                $HTML = $global:ie.Document.body.innerHTML.ToString()
                $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

                $attempts++
            }

            if ($null -eq $downloadLink) {
                Write-Warning "The script has reached the max number of attempts to retrieve download link."
                return $null
            }

            return $downloadLink
        }
        'LOLComponents' {
            $HTML = Invoke-RestMethod 'https://www.linz.govt.nz/guidance/landonline-support/legacy-landonline-support/software-downloads-and-installation/software-downloads'
            $HTML > C:\Code\AppMani\html.html
            $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
        }
        '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 = 'http://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-Warning "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-Warning "The script ran into an issue: $($Error[0])"
                    return $null
                }
            }
            else {
                Write-Warning "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-Warning "The script ran into an issue: $($Error[0])"
                return $null
            }

            if ($links) {
                $downloadLink = $links | Where-Object { ($_ -match "$Architecture/(.+?)-installer\.msi$") }
            }
            else {
                Write-Warning "No links found."
                return $null
            }

            if ($downloadLink) {
                return $downloadLink
            }
            else {
                Write-Warning "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-Warning "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-Warning "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-Host "No matching function to retrieve download link for $program"
            return $null
        }
    }
}

Function Confirm-InstallerValidity  {
    Param(
        $FilePath
    )

    $varChain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain
    try {
        $varChain.Build((Get-AuthenticodeSignature -FilePath "$FilePath").SignerCertificate) #| out-null
    } catch [System.Management.Automation.MethodInvocationException] {
        $err = ( "ERROR: '$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-Warning $err -ForegroundColor white -BackgroundColor red
        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 'Compare-Versions'
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 'Confirm-InstallerValidity'