WinSoftwareUpdate.psm1

<#
    MIT License

    Copyright (C) 2023 Robin Stolpe.

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
#>

Function Update-RSWinSoftware {
    <#
        .SYNOPSIS
        This module let users auto update their installed software on Windows 10, 11 with WinGet.

        .DESCRIPTION
        The module will check if WinGet is installed and up to date, if not it will install WinGet or update it.
        It will also if Microsoft.VCLibs is installed and if not it will install it.
        Besides that the module will check what aritecture the computer is running and download the correct version of Microsoft.VCLibs etc.
        Then it will check if there is any software that needs to be updated and if so it will update them.

        .PARAMETER SkipVersionCheck
        You can decide if you want to skip the WinGet version check, default it set to false. If you use the switch -SkipVersionCheck it will skip to check the version of WinGet.

        .EXAMPLE
        Update-RSWinSoftware
        # This command will run the module and check if WinGet and VCLibs are up to date.

        .EXAMPLE
        Update-RSWinSoftware -SkipVersionCheck
        # This command will run the module without checking if WinGet and VCLibs are up to date.

        .LINK
        https://github.com/rstolpe/WinSoftwareUpdate/blob/main/README.md

        .NOTES
        Author: Robin Stolpe
        Mail: robin@stolpe.io
        Twitter: https://twitter.com/rstolpes
        Linkedin: https://www.linkedin.com/in/rstolpe/
        Website/Blog: https://stolpe.io
        GitHub: https://github.com/rstolpe
        PSGallery: https://www.powershellgallery.com/profiles/rstolpe
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false, HelpMessage = "Decide if you want to skip the WinGet version check, default it set to false")]
        [switch]$SkipVersionCheck = $false
    )

    #Check if script was started as Administrator
    if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator")) {
        Write-Error ("{0} needs admin privileges, exiting now...." -f $MyInvocation.MyCommand)
        break
    }

    # =================================
    # Static Variables
    # =================================
    #
    # GitHub url for the latest release
    [string]$GitHubUrl = "https://api.github.com/repos/microsoft/winget-cli/releases/latest"
    #
    # The headers and API version for the GitHub API
    [hashtable]$GithubHeaders = @{
        "Accept"               = "application/vnd.github.v3+json"
        "X-GitHub-Api-Version" = "2022-11-28"
    }
    #
    #
    [string]$VCLibsOutFile = "$env:TEMP\Microsoft.VCLibs.140.00.$($Arch).appx"

    # Importing appx with -usewindowspowershell if your using PowerShell 7 or higher
    if ($PSVersionTable.PSVersion.Major -ge 7) {
        import-module appx -usewindowspowershell
        Write-Output "`n=== This messages is expected if you are using PowerShell 7 or higher ===`n"
    }

    # Getting system information
    [System.Object]$SysInfo = Get-RSInstallInfo
    # If user has choosen to skip the WinGet version don't check, if WinGet is not installed this will install WinGet anyway.
    if ($SkipVersionCheck -eq $false -or $SysInfo.WinGet -eq "0.0.0.0") {
        Confirm-RSWinGet -GitHubUrl $GitHubUrl -GithubHeaders $GithubHeaders -WinGet $SysInfo.WinGet
    }

    # If VCLibs are not installed it will get installed
    if ($null -eq $SysInfo.VCLibs) {
        Install-RSVCLibs -VCLibsUrl $SysInfo.VCLibsUrl -VCLibsOutFile $VCLibsOutFile
    }

    # Starts to check for updates of the installed software
    Start-RSWinGet

    Write-OutPut "`n=== \\\ Script Finished /// ===`n"
}
Function Confirm-RSWinGet {
    <#
        .SYNOPSIS
        This function is connected and used of the main function for this module, Update-RSWinSoftware.
        So when you run the Update-RSWinSoftware function this function will be called during the process.

        .DESCRIPTION
        This function will connect to the GitHub API and check if there is a newer version of WinGet to download and install.

        .PARAMETER GitHubUrl
        Url to the GitHub API for the latest release of WinGet

        .PARAMETER GithubHeaders
        The headers and API version for the GitHub API, this is pasted from the main function for this module, Update-RSWinSoftware.
        This is pasted in from the main function for this module, Update-RSWinSoftware.

        .PARAMETER WinGet


        .LINK
        https://github.com/rstolpe/WinSoftwareUpdate/blob/main/README.md

        .NOTES
        Author: Robin Stolpe
        Mail: robin@stolpe.io
        Twitter: https://twitter.com/rstolpes
        Linkedin: https://www.linkedin.com/in/rstolpe/
        Website/Blog: https://stolpe.io
        GitHub: https://github.com/rstolpe
        PSGallery: https://www.powershellgallery.com/profiles/rstolpe
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, HelpMessage = "The GitHub API url for the latest release of WinGet")]
        [string]$GitHubUrl,
        [Parameter(Mandatory = $true, HelpMessage = "The headers and API version for the GitHub API")]
        [hashtable]$GithubHeaders,
        [Parameter(Mandatory = $false, HelpMessage = "Information about the installed version of WinGet")]
        $WinGet
    )

    if ($WinGet -eq "No") {
        Write-Output "WinGet is not installed, downloading and installing WinGet..."
    }
    else {
        Write-OutPut "Checking if it's any newer version of WinGet to download and install..."
    }

    # Collecting information from GitHub regarding latest version of WinGet
    try {
        # If the computer is running PowerShell 7 or higher, use HTTP/3.0 for the GitHub API in other cases use HTTP/2.0
        if ($PSVersionTable.PSVersion.Major -ge 7) {
            [System.Object]$GithubInfoRestData = Invoke-RestMethod -Uri $GitHubUrl -Method Get -Headers $GithubHeaders -TimeoutSec 10 -HttpVersion 3.0 | Select-Object -Property assets, tag_name
        }
        else {
            [System.Object]$GithubInfoRestData = Invoke-RestMethod -Uri $GitHubUrl -Method Get -Headers $GithubHeaders -TimeoutSec 10 | Select-Object -Property assets, tag_name
        }

        [System.Object]$GitHubInfo = [PSCustomObject]@{
            Tag         = $($GithubInfoRestData.tag_name.Substring(1))
            DownloadUrl = $GithubInfoRestData.assets | where-object { $_.name -like "*.msixbundle" } | Select-Object -ExpandProperty browser_download_url
            OutFile     = "$env:TEMP\WinGet_$($GithubInfoRestData.tag_name.Substring(1)).msixbundle"
        }
    }
    catch {
        Write-Error @"
   "Message: "$($_.Exception.Message)`n
   "Error Line: "$($_.InvocationInfo.Line)`n
"@

        break
    }

    # Checking if the installed version of WinGet are the same as the latest version of WinGet
    [version]$vWinGet = [string]$SysInfo.WinGet
    [version]$vGitHub = [string]$GitHubInfo.Tag
    if ($WinGet -ne "1.19.3531.0") {
        if ([Version]$vWinGet -lt [Version]$vGitHub) {
            Write-Output "WinGet has a newer version $($GitHubInfo.Tag), downloading and installing it..."
            Invoke-WebRequest -UseBasicParsing -Uri $GitHubInfo.DownloadUrl -OutFile $GitHubInfo.OutFile

            Write-Verbose "Installing version $($GitHubInfo.Tag) of WinGet..."
            Add-AppxPackage $($GitHubInfo.OutFile)
        }
        else {
            Write-OutPut "Your already on the latest version of WinGet $($WinGet), no need to update."
        }
    }
}
Function Get-RSInstallInfo {
    <#
        .SYNOPSIS
        This function is connected and used of the main function for this module, Update-RSWinSoftware.
        So when you run the Update-RSWinSoftware function this function will be called during the process.

        .DESCRIPTION
        This function will collect the following data from the computer and store it in a PSCustomObject to make it easier for the main function for this module, Update-RSWinSoftware, to use the data.

        .LINK
        https://github.com/rstolpe/WinSoftwareUpdate/blob/main/README.md

        .NOTES
        Author: Robin Stolpe
        Mail: robin@stolpe.io
        Twitter: https://twitter.com/rstolpes
        Linkedin: https://www.linkedin.com/in/rstolpe/
        Website/Blog: https://stolpe.io
        GitHub: https://github.com/rstolpe
        PSGallery: https://www.powershellgallery.com/profiles/rstolpe
    #>


    <## Checking what architecture your running
    # To Install visualcredist use vc_redist.x64.exe /install /quiet /norestart
    # Now we also need to verify that's the latest version and then download and install it if the latest version is not installed
    # When this is added no need to install Microsoft.VCLibs as it's included in the VisualCRedist
    # Don't have the time for it now but this will be added later#>



    # Getting architecture of the computer and adapting it after the right download links
    [string]$Architecture = $(Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty SystemType)
    switch ($Architecture) {
        "x64-based PC" {
            [string]$VisualCRedistUrl = "https://aka.ms/vs/17/release/vc_redist.x64.exe"
            [string]$VCLibsUrl = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"
            [string]$Arch = "x64"
        }
        "ARM64-based PC" {
            [string]$VisualCRedistUrl = "https://aka.ms/vs/17/release/vc_redist.arm64.exe"
            [string]$VCLibsUrl = "https://aka.ms/Microsoft.VCLibs.arm64.14.00.Desktop.appx"
            [string]$Arch = "arm64"
        }
        "x86-based PC" {
            [string]$VisualCRedistUrl = "https://aka.ms/vs/17/release/vc_redist.x86.exe"
            [string]$VCLibsUrl = "https://aka.ms/Microsoft.VCLibs.x86.14.00.Desktop.appx"
            [string]$Arch = "arm64"
        }
        default {
            Write-Error "Your running a unsupported architecture, exiting now..."
            break
        }
    }

    # Collects everything in pscustomobject to get easier access to the information
    [System.Object]$SysInfo = [PSCustomObject]@{
        VCLibs           = $(Get-AppxPackage -Name "Microsoft.VCLibs.140.00" -AllUsers | Where-Object { $_.Architecture -eq $Arch })
        WinGet           = $(try { (Get-AppxPackage -AllUsers | Where-Object { $_.PackageFamilyName -like "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe" } | Sort-Object { $_.Version -as [version] } -Descending | Select-Object Version -First 1).version } catch { "0.0.0.0" })
        VisualCRedistUrl = $VisualCRedistUrl
        VCLibsUrl        = $VCLibsUrl
        Arch             = $Arch
    }

    return $SysInfo
}
Function Install-RSVCLib {
    <#
        .SYNOPSIS
        This function is connected and used of the main function for this module, Update-RSWinSoftware.
        So when you run the Update-RSWinSoftware function this function will be called during the process.

        .DESCRIPTION
        This function will install VCLibs if it's not installed on the computer.

        .PARAMETER VCLibsOutFile
        The path to the output file for the VCLibs when downloaded, this is pasted from the main function for this module, Update-RSWinSoftware.

        .PARAMETER VCLibsUrl
        The url path to where the VCLibs can be downloaded from, this is pasted from the main function for this module, Update-RSWinSoftware.

        .LINK
        https://github.com/rstolpe/WinSoftwareUpdate/blob/main/README.md

        .NOTES
        Author: Robin Stolpe
        Mail: robin@stolpe.io
        Twitter: https://twitter.com/rstolpes
        Linkedin: https://www.linkedin.com/in/rstolpe/
        Website/Blog: https://stolpe.io
        GitHub: https://github.com/rstolpe
        PSGallery: https://www.powershellgallery.com/profiles/rstolpe
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, HelpMessage = "The path to the output file for the VCLibs when downloaded")]
        [string]$VCLibsOutFile,
        [Parameter(Mandatory = $true, HelpMessage = "Url path to where the VCLibs can be downloaded from")]
        [string]$VCLibsUrl
    )

    try {
        Write-Output "Microsoft.VCLibs is not installed, downloading and installing it now..."
        Invoke-WebRequest -UseBasicParsing -Uri $VCLibsUrl -OutFile $VCLibsOutFile

        Add-AppxPackage $VCLibsOutFile
    }
    catch {
        Write-Error "Something went wrong when trying to install Microsoft.VCLibs..."
        Write-Error @"
   "Message: "$($_.Exception.Message)`n
   "Error Line: "$($_.InvocationInfo.Line)`n
"@

        break
    }
}
Function Start-RSWinGet {
    <#
        .SYNOPSIS
        This function is connected and used of the main function for this module, Update-RSWinSoftware.
        So when you run the Update-RSWinSoftware function this function will be called during the process.

        .DESCRIPTION
        This will function will update all sources for WinGet and then check if any softwares needs to be updated.

        .LINK
        https://github.com/rstolpe/WinSoftwareUpdate/blob/main/README.md

        .NOTES
        Author: Robin Stolpe
        Mail: robin@stolpe.io
        Twitter: https://twitter.com/rstolpes
        Linkedin: https://www.linkedin.com/in/rstolpe/
        Website/Blog: https://stolpe.io
        GitHub: https://github.com/rstolpe
        PSGallery: https://www.powershellgallery.com/profiles/rstolpe
    #>


    Write-Output "Making sure that WinGet has the latest source list"
    WinGet.exe source update

    Write-OutPut "Checks if any softwares needs to be updated`n"
    try {
        WinGet.exe upgrade --all --silent --accept-source-agreements --include-unknown
        Write-Output "Everything is now completed, you can close this window"
    }
    catch {
        Write-Error @"
   "Message: "$($_.Exception.Message)`n
   "Error Line: "$($_.InvocationInfo.Line)`n
"@

    }
}