PackageUpdateInfo.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\PackageUpdateInfo.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
if ($PackageUpdateInfo_dotsourcemodule) { $script:doDotSource = $true } else { $script:doDotSource = $false }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
if ($PackageUpdateInfo_importIndividualFiles) { $importIndividualFiles = $true } else { $importIndividualFiles = $false}
if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }

function Import-ModuleFile {
    <#
        .SYNOPSIS
            Loads files into the module on module import.
 
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
 
            This provides a central location to react to files being imported, if later desired
 
        .PARAMETER Path
            The path to the file to load
 
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
 
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )

    $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
    if ($doDotSource) { . $resolvedPath }
    else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) }
}

#region Load individual files
if ($importIndividualFiles) {
    # Execute Preimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) {
        . Import-ModuleFile -Path $path
    }

    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) {
        . Import-ModuleFile -Path $function.FullName
    }

    # Import all public functions
    $functions = (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)
    foreach ($function in $functions) {
        . Import-ModuleFile -Path $function.FullName
    }

    # Execute Postimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) {
        . Import-ModuleFile -Path $path
    }

    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
# Place all code that should be run before functions are imported here


#region try to import module BurntToast, to enable Windows 10 Toast Notifications
$optionalModule = @{
    ModuleName    = 'BurntToast'
    ModuleVersion = '0.6.3'
}
$script:EnableToastNotification = $true

$moduleToLoad = Get-Module -Name $optionalModule.ModuleName -ListAvailable -Verbose:$false | Where-Object { $_.Version -ge [version]$optionalModule.ModuleVersion } | Sort-Object -Property Version | Select-Object -Last 1
if ($moduleToLoad) {
    try {
        $moduleToLoad | Import-Module -ErrorAction Stop
    } catch {
        Write-Warning "Fail to import optional module $($moduleToLoad.Name) ($($moduleToLoad.Version)). Unable to show Toast Notifications even when the module is present."
        $script:EnableToastNotification = $false
    }
} else {
    if (($PSVersionTable.PSEdition -like "Desktop") -or ($PSVersionTable.PSEdition -like "Core" -and $isWindows)) {
        if ((Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue).Version -ge [version]'10.0') {
            Write-Warning "Missing the optional module '$($optionalModule.ModuleName)'. Unable to show Toast Notifications."
            Write-Warning "It is recommended to install module $($optionalModule.ModuleName) (min. v$($optionalModule.ModuleVersion)) to enable the possibility to enable Windows 10 toast notication for PackageUpdateInfo."
            Write-Warning "( Install-Module -Name $($optionalModule.ModuleName) -MinimumVersion $($optionalModule.ModuleVersion) )"
        }
    }
    $script:EnableToastNotification = $false
}
#endregion try to import module BurntToast, to enable Windows 10 Toast Notifications

function Get-VersionDifference {
    <#
    .SYNOPSIS
        Get-VersionDifference
 
    .DESCRIPTION
        Subtracts two version objects from each other to get the difference
 
    .PARAMETER LowerVersion
        The module info from the local existing version
 
    .PARAMETER HigherVersion
        The module info from the online existing version
 
    .EXAMPLE
        PS C:\> Get-VersionDifference -LowerVersion "1.0.0.0" -HigherVersion "1.1.2.3"
 
        Return 0.1.2.3 as difference between the both versions
    #>

    [CmdletBinding()]
    [OutputType([version])]
    param (
        [version]
        $LowerVersion,

        [version]
        $HigherVersion
    )

    $versionDiffMajor = $HigherVersion.Major - $LowerVersion.Major
    $versionDiffMinor = $HigherVersion.Minor - $LowerVersion.Minor
    $versionDiffBuild = $HigherVersion.Build - $LowerVersion.Build
    $versionDiffRevision = $HigherVersion.Revision - $LowerVersion.Revision

    if ($versionDiffMajor -lt 0) { $versionDiffMajor = 0 }
    if ($versionDiffMinor -lt 0) { $versionDiffMinor = 0 }
    if ($versionDiffBuild -lt 0) { $versionDiffBuild = 0 }
    if ($versionDiffRevision -lt 0) { $versionDiffRevision = 0 }

    $versionDiff = [version]::new($versionDiffMajor, $versionDiffMinor, $versionDiffBuild, $versionDiffRevision)

    $versionDiff
}

function Show-ToastNotification {
    <#
    .SYNOPSIS
        Create a new Toast Notification from a PackUpdateInfo object
 
    .DESCRIPTION
        Create a new Toast Notification from a PackUpdateInfo object
        Helper function used for internal commands.
 
    .PARAMETER PackageUpdateInfo
        The PackageUpdate.Info object to show in the toast notification
 
    .EXAMPLE
        PS C:\> Show-ToastNotification -PackageUpdateInfo $PackageUpdateInfo
 
        Show Toast Notification on modules with outstanding updates
    #>

    [CmdletBinding( DefaultParameterSetName = 'Default', SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    [Alias()]
    [OutputType([PackageUpdate.Info])]
    param (
        [Parameter(ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [PackageUpdate.Info[]]
        $PackageUpdateInfo
    )

    begin {
    }

    process {
        # general toast notification header
        $toastHeader = New-BTHeader -Id '001' -Title 'PackageUpdateInfo - Update available' -ActivationType Foreground

        # the text
        $toastText = @()
        $toastText += $PackageUpdateInfo.Repository + "\" + $PackageUpdateInfo.Name + " v" + $PackageUpdateInfo.VersionOnline
        $toastText += "Published: " + $PackageUpdateInfo.PublishedDate.ToString() + "`n" + "Installed version: v" + $PackageUpdateInfo.VersionInstalled + "$(if (-not $PackageUpdateInfo.HasReleaseNotes) { "`n(No release notes available)" })"
        if ($PackageUpdateInfo.IsCurrentUserPath) {
            $toastText += "This is a user specific module."
        } else {
            $toastText += "This is machine wide module."
        }

        # the logo
        if ($PackageUpdateInfo.IconUri) {
            $iconPath = Join-Path -Path $script:ModuleTempPath -ChildPath $PackageUpdateInfo.IconUri.Segments[-1]
            if (Test-Path -Path $iconPath) { Remove-Item -Path $iconPath -Force -ErrorAction:SilentlyContinue }

            try {
                Invoke-WebRequest -Uri $PackageUpdateInfo.IconUri -OutFile $iconPath -SkipCertificateCheck -SkipHeaderValidation -ErrorAction Stop
                $toastLogo = $iconPath
            } catch {
                Write-Verbose -Message "Warning! Unable to get icon from '$($PackageUpdateInfo.IconUri)' for module '$($PackageUpdateInfo.Name)'"
                $toastLogo = $script:ModuleIconPath
            }
        } else {
            $toastLogo = $script:ModuleIconPath
        }

        # the buttons
        $toastButton = @()
        #$toastButton += New-BTButton -Content 'Install' -Arguments "C:\Windows\notepad.exe"
        if ($PackageUpdateInfo.HasReleaseNotes) {
            if ($PackageUpdateInfo.ReleaseNotesIsUri) {
                $toastButtonArgument = $PackageUpdateInfo.ReleaseNotes
            } else {
                $toastButtonArgument = "$($script:ModuleTempPath)\$($PackageUpdateInfo.Name)_v$($PackageUpdateInfo.VersionOnline)_$(Get-Date -Format 'yyyyMMddHHmmssfff').txt"
                Set-Content -Path $toastButtonArgument -Value $PackageUpdateInfo.ReleaseNotes -Force -Encoding Default
            }
            $toastButton += New-BTButton -Content 'Release notes' -Arguments $toastButtonArgument
        }
        $toastButton += New-BTButton -Dismiss

        # create the toast notification
        $notificationParams = @{
            Header  = $toastHeader
            Text    = $toastText
            AppLogo = $toastLogo
            Button  = $toastButton
        }
        New-BurntToastNotification @notificationParams
    }

    end {
    }
}

function Test-UpdateIsNeeded {
    <#
    .SYNOPSIS
        Test-UpdateIsNeeded
 
    .DESCRIPTION
        Command to test if a module version update appears as "update needed".
        If "needed" depends on the set rules and report-on-different-version parts.
 
    .PARAMETER ModuleLocal
        The module info from the local existing version
 
    .PARAMETER ModuleOnline
        The module info from the online existing version
 
    .EXAMPLE
        PS C:\> Test-UpdateIsNeeded -ModuleLocal $moduleLocal -ModuleOnline $moduleOnline
 
        Check if URI is a URI that can be covered for plain text release notes
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param (
        $ModuleLocal,

        $moduleOnline
    )

    $versionDiff = Get-VersionDifference -LowerVersion $ModuleLocal.Version -HigherVersion $moduleOnline.Version
    $name = $ModuleLocal.Name

    $rules = Get-PackageUpdateRule -IncludeDefaultRule | Sort-Object -Property Id -Descending

    foreach ($rule in $rules) {
        Write-Verbose -Message "Working on rule $($rule.Id)"
        $stop = $false
        # check for exclude and abort further processing for rule
        foreach ($exclude in $rule.ExcludeModuleFromChecking) {
            if ($name -like $exclude) {
                $stop = $true
                Write-Verbose -Message "Rule $($rule.Id) does match exclude pattern ($([string]::Join(", ", $rule.ExcludeModuleFromChecking))) for module $($name)"
            }
        }
        if ($stop) { continue }

        $stop = $true
        # check for inclue to declare, the rule fits for processing
        foreach ($include in $rule.IncludeModuleForChecking) {
            if ($name -like $include) {
                $stop = $false
                Write-Verbose -Message "Rule $($rule.Id) does match include pattern ($([string]::Join(", ", $rule.IncludeModuleForChecking))) for module $($name)"
            }
        }
        if ($stop) { continue }

        # Version checking and return boolean
        $higherChange = $false
        if ([bool]$versionDiff.Major) {
            if ($rule.ReportChangeOnMajor) {
                Write-Verbose -Message "Major version change in module $($name) found. (Diff: $($versionDiff))"
                return $true
            } else { $higherChange = $true }
        }
        if ([bool]$versionDiff.Minor) {
            if ($rule.ReportChangeOnMinor -and (-not $higherChange)) {
                Write-Verbose -Message "Minor version change in module $($name) found. (Diff: $($versionDiff))"
                return $true
            } else { $higherChange = $true }
        }
        if ([bool]$versionDiff.Build) {
            if ($rule.ReportChangeOnBuild -and (-not $higherChange)) {
                Write-Verbose -Message "Build version change in module $($name) found. (Diff: $($versionDiff))"
                return $true
            } else { $higherChange = $true }
        }
        if ([bool]$versionDiff.Revision) {
            if ($rule.ReportChangeOnRevision -and (-not $higherChange)) {
                Write-Verbose -Message "Revision version change in module $($name) found. (Diff: $($versionDiff))"
                return $true
            } else { $higherChange = $true }
        }

        # Arriving here means, the name filter apply and version change did not result in status for "report-update-needed"
        # Function return $false, due to there is an update present, but compairing to the defined rule(s), reporting on the version change is suppressed
        return $false
    }
}

function ConvertFrom-PackageUpdateSetting {
    <#
    .SYNOPSIS
        ConvertFrom-PackageUpdateSetting
 
    .DESCRIPTION
        Convert from a PackageUpdateSetting object to a PSCustomObject
 
    .PARAMETER InputObject
        The PackageUpdateSetting object to convert
 
    .PARAMETER AsHashTable
        Output is done as hashtable, not as PSObject
 
    .EXAMPLE
        PS C:\> ConvertFrom-PackageUpdateSetting -InputObject (Get-PackageUpdateSetting)
 
        Check if URI is a URI that can be covered for plain text release notes
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "SetBehaviour")]
        [PackageUpdate.Configuration]
        $InputObject,

        [switch]
        $AsHashTable
    )

    begin {
    }

    process {
        $hash = [ordered]@{ }

        $notToString = @("System.Boolean", "System.String[]", "System.Int", "PackageUpdate.ModuleRule", "PackageUpdate.ModuleRule[]")
        foreach ($property in $InputObject.psobject.Properties) {
            if ($property.TypeNameOfValue -in $notToString) {
                $hash[$property.Name] = $property.Value
            } else {
                $hash[$property.Name] = [String]($property.Value)
            }
        }

        if ($AsHashTable) {
            $hash
        } else {
            [PSCustomObject]$hash
        }
    }

    end {
    }
}

function ConvertTo-PackageUpdateSetting {
    <#
    .SYNOPSIS
        ConvertFrom-PackageUpdateSetting
 
    .DESCRIPTION
        Convert from a PackageUpdateSetting object to a PSCustomObject
 
    .PARAMETER InputObject
        PSCustomObject object to convert
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateSetting | ConvertFrom-PackageUpdateSetting | ConvertTo-PackageUpdateSetting
 
        Check if URI is a URI that can be covered for plain text release notes
    #>

    [CmdletBinding()]
    [OutputType([PackageUpdate.Configuration])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "SetBehaviour")]
        [PSCustomObject]
        $InputObject
    )

    begin {
    }

    process {
        $hash = [ordered]@{ }

        foreach ($property in $InputObject.psobject.Properties) {
            $hash[$property.Name] = $property.Value
        }

        [PackageUpdate.Configuration]$hash
    }

    end {
    }
}

function Assert-PossibleReleaseNotesURI {
    <#
    .SYNOPSIS
        Assert-PossibleReleaseNotesURI
 
    .DESCRIPTION
        Check if URI is a URI that can be covered for plain text release notes
 
    .PARAMETER URI
        The URI to check
 
    .EXAMPLE
        PS C:\> Assert-PossibleReleaseNotesURI -URI 'https://github.com/AndiBellstedt/PackageUpdateInfo/blob/master/PackageUpdateInfo/changelog.md'
 
        Check if URI is a URI that can be covered for plain text release notes
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param (
        [string]
        $URI
    )

    if($URI -like "https://github.com*" -or $URI -like "https://raw.githubusercontent.com*" -or $URI -like "https://gitlab.com*") {
        $true
    } else {
        $false
    }
}

function Add-PackageUpdateRule {
    <#
    .SYNOPSIS
        Add rule for checking and reporting on installed modules
 
    .DESCRIPTION
        This command allows to declare how a modules is handled in reporting for special.
 
        For example, you can configure PackageUpdateINfo to suppress revision updates on a frequent
        updated module, so that only build, minor or major updates are reportet as "update needed".
 
    .PARAMETER Id
        The Id as an identifier for the rule
 
    .PARAMETER ExcludeModuleFromChecking
        ModuleNames to exclude from update checking
 
    .PARAMETER IncludeModuleForChecking
        ModuleNames to include from update checking
        By default all modules are included.
 
        Default value is: "*"
 
    .PARAMETER ReportChangeOnMajor
        Report when major version changed for a module
 
        This means 'Get-PackageUpdateSetting' report update need,
        only when the major version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        1 0 0 0
 
    .PARAMETER ReportChangeOnMinor
        Report when minor version changed for a module
 
        This means 'Get-PackageUpdateSetting' report update need,
        only when the minor version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        0 1 0 0
 
    .PARAMETER ReportChangeOnBuild
        Report when build version changed for a module
 
        This means 'Get-PackageUpdateSetting' report update need,
        when the build version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        0 0 1 0
 
    .PARAMETER ReportChangeOnRevision
        Report when revision part changed for a module
 
        This means 'Get-PackageUpdateSetting' report update need,
        when the revision version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        1 0 0 0
 
    .PARAMETER SettingObject
        Settings object parsed in from command Get-PackageUpdateSetting
        This is an optional parameter. By default it will use the default
        settings object from the module.
 
    .PARAMETER PassThru
        The rule object will be parsed to the pipeline for further processing
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Add-PackageUpdateRule -IncludeModuleForChecking "MyModule" -ReportChangeOnMajor $true -ReportChangeOnMinor $true -ReportChangeOnBuild $true -ReportChangeOnRevision $false
 
        Add a new custom rule for "MyModule" to supress notifications on revision updates of the module
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [Alias('apur')]
    [OutputType([PackageUpdate.ModuleRule])]
    Param (
        [ValidateRange(1, [int]::MaxValue)]
        [int]
        $Id,

        [Alias("Include", "IncludeModule")]
        [String[]]
        $IncludeModuleForChecking,

        [Alias("Exclude", "ExcludeModule")]
        [AllowEmptyString()]
        [String[]]
        $ExcludeModuleFromChecking,

        [bool]
        $ReportChangeOnMajor = $true,

        [bool]
        $ReportChangeOnMinor = $true,

        [bool]
        $ReportChangeOnBuild = $true,

        [bool]
        $ReportChangeOnRevision = $true,

        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PackageUpdate.Configuration]
        $SettingObject,

        [switch]
        $PassThru
    )

    begin {
    }

    process {
        # If no setting object is piped in, get the current settings
        if (-not $SettingObject) { $SettingObject = Get-PackageUpdateSetting }
        if ($Id) {
            if ($Id -in $SettingObject.CustomRule.Id) {
                Write-Error -Message "Unable to add rule with Id $($Id), because a rule with this Id already exist." -ErrorAction Stop
            }
        }
        if ($ExcludeModuleFromChecking) {
            foreach ($item in $SettingObject.CustomRule) {
                foreach ($toExclude in $ExcludeModuleFromChecking) {
                    if ($toExclude -in $item.ExcludeModuleFromChecking) {
                        Write-Error -Message "Unable to add rule with exclude module '$($toExclude)', because a rule with this module as excluded module already exist." -ErrorAction Stop
                    }
                }
            }
        }
        if ($IncludeModuleForChecking) {
            foreach ($item in $SettingObject.CustomRule) {
                foreach ($toInclude in $IncludeModuleForChecking) {
                    if ($toInclude -in $item.IncludeModuleForChecking) {
                        Write-Error -Message "Unable to add rule with include module '$($toInclude)', because a rule with this module to include already exist." -ErrorAction Stop
                    }
                }
            }
        }

        $rule = [PackageUpdate.ModuleRule]::new()

        # Set the rule properties values
        if ("Id" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
            Write-Verbose "Setting Id: '$($Id)'"
            $rule.Id = $Id
        } else {
            $Id = ($SettingObject.CustomRule.Id | Sort-Object | Select-Object -Last 1) + 1
            Write-Verbose "Setting Id: '$($Id)'"
            $rule.Id = $Id
        }

        if ("ExcludeModuleFromChecking" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
            Write-Verbose "Setting ExcludeModuleFromChecking: '$([string]::Join(", ", $ExcludeModuleFromChecking))'"
            $rule.ExcludeModuleFromChecking = $ExcludeModuleFromChecking
        }
        if ("IncludeModuleForChecking" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
            Write-Verbose "Setting IncludeModuleForChecking: '$([string]::Join(", ", $IncludeModuleForChecking))'"
            $rule.IncludeModuleForChecking = $IncludeModuleForChecking
        }

        if ("ReportChangeOnMajor" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) { Write-Verbose "Setting ReportChangeOnMajor: $($ReportChangeOnMajor)" }
        $rule.ReportChangeOnMajor = $ReportChangeOnMajor
        if ("ReportChangeOnMinor" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) { Write-Verbose "Setting ReportChangeOnMinor: $($ReportChangeOnMinor)" }
        $rule.ReportChangeOnMinor = $ReportChangeOnMinor
        if ("ReportChangeOnBuild" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) { Write-Verbose "Setting ReportChangeOnBuild: $($ReportChangeOnBuild)" }
        $rule.ReportChangeOnBuild = $ReportChangeOnBuild
        if ("ReportChangeOnRevision" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) { Write-Verbose "Setting ReportChangeOnRevision: $($ReportChangeOnRevision)" }
        $rule.ReportChangeOnRevision = $ReportChangeOnRevision

        $SettingObject.CustomRule += $rule

        if ($pscmdlet.ShouldProcess($SettingObject.Path, "Add custom ModuleRule")) {
            $SettingObject | ConvertFrom-PackageUpdateSetting | ConvertTo-Json | Out-File -FilePath $SettingObject.Path -Encoding default -Force
        }

        if($PassThru) {
            [PackageUpdate.ModuleRule]$rule
        }
    }

    end {
    }
}


function Export-PackageUpdateInfo {
    <#
    .SYNOPSIS
        Export PackageUpdateInfo to a data file
 
    .DESCRIPTION
        Export PackageUpdateInfo to a data file
 
    .PARAMETER InputObject
        The PackageUpdateInfo from Get-PackageUpdateInfo function.
 
    .PARAMETER Path
        The filepath where to export the infos.
        Please specify a file as path.
 
        Default path value is:
        Linux: "$HOME/.local/share/powershell/PackageUpdateInfo/PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml")
        Windows: "$HOME\AppData\Local\Microsoft\Windows\PowerShell\PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml")
 
    .PARAMETER OutputFormat
        The output format for the data
        Available formats are "XML","JSON","CSV"
 
    .PARAMETER Encoding
        File Encoding for the file
 
    .PARAMETER Force
        If the directory for the file is not present, but a directory other then the default is specified,
        the function will try to create the diretory.
 
    .PARAMETER Append
        The output file will not be replaced. All information will be appended.
 
    .PARAMETER IncludeTimeStamp
        A timestamp will be added to the information records.
 
    .PARAMETER PassThru
        The exported objects will be parsed to the pipeline for further processing.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateInfo | Export-PackageUpdateInfo
 
        Example for usage of Export-PackageUpdateInfo
    #>

    [CmdletBinding( SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium')]
    [Alias('epui')]
    [OutputType([PackageUpdate.Info])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PackageUpdate.Info[]]
        $InputObject,

        [Parameter(Position = 0)]
        [Alias("FullName", "FilePath")]
        [String]
        $Path,

        [ValidateSet("XML", "JSON", "CSV")]
        [Alias("Format")]
        [String]
        $OutputFormat = "XML",

        [ValidateSet("default", "utf7", "utf8", "utf32", "unicode", "ascii", "string", "oem", "bigendianunicode")]
        [String]
        $Encoding = "default",

        [switch]
        $Force,

        [switch]
        $Append,

        [switch]
        $IncludeTimeStamp,

        [switch]
        $PassThru
    )

    begin {
        # Set path variable to default value, when not specified
        if (-not $path) {
            if ($IsLinux) {
                $path = (Join-Path $HOME ".local/share/powershell/PackageUpdateInfo/PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml")
            } else {
                $path = (Join-Path $HOME "AppData\Local\Microsoft\Windows\PowerShell\PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml")
            }
        }

        # If file is specified as path
        if (Test-Path -Path $Path -PathType Leaf -IsValid) {
            # If file is present, resolve the path
            if (Test-Path -Path $Path -PathType Leaf) {
                $outputPath = Resolve-Path -Path $Path
            } else {
                # If Force switch is specified and the path does not exists
                if ($Force -and (-not (Resolve-Path -Path (Split-Path $Path -ErrorAction SilentlyContinue))) ) {
                    $null = New-Item -ItemType Directory -Path (Split-Path $Path) -ErrorAction Stop
                }
                # Try to create the file and resolve the path
                $outputPath = New-Item -ItemType File -Path $Path -ErrorAction Stop
                $outputPath = Resolve-Path -Path $outputPath
            }
        } elseif (Test-Path -Path $Path -PathType Container) {
            # If directory is specified as path
            Write-Error -Message "Specified Path is a directory. Please specify an file." -ErrorAction Stop
        } else {
            Write-Error -Message "Specified Path is an invalid directory. Please specify an valid file as output path." -ErrorAction Stop
        }

        $output = @()
    }

    process {
        if ($IncludeTimeStamp) {
            $InputObject | Add-Member -MemberType NoteProperty -Name TimeStamp -Value (Get-Date -Format s) -Force
        }

        if ($OutputFormat -in "JSON", "CSV") {
            $output += foreach ($object in $InputObject) {
                $hash = [ordered]@{
                    Name             = $object.Name
                    Repository       = $object.Repository
                    VersionInstalled = $object.VersionInstalled.ToString()
                    VersionOnline    = $object.VersionOnline.ToString()
                    NeedUpdate       = $object.NeedUpdate
                    Path             = $object.Path
                    ProjectUri       = $object.ProjectUri
                    IconUri          = $object.IconUri
                    ReleaseNotes     = $object.ReleaseNotes
                    Author           = $object.Author
                    PublishedDate    = ($object.PublishedDate | Get-Date -Format "yyyy-MM-dd HH:mm:ss")
                    Description      = $object.Description
                }
                if ($IncludeTimeStamp) {
                    $hash.add("TimeStamp", $object.TimeStamp)
                }
                New-Object -TypeName psobject -Property $hash
            }
        } else {
            $output += $InputObject
        }
    }

    end {
        if ($output) {
            if ($pscmdlet.ShouldProcess($outputPath, "Export PackageUpdateInfo")) {
                $outFileParams = @{
                    Encoding = $Encoding
                }
                if ($Append -and $OutputFormat -notlike "XML") { $outFileParams.Add("Append", $true) }

                if ($OutputFormat -in "JSON") {
                    $outFileParams.Add("FilePath", $outputPath.Path)
                    $output | ConvertTo-Json | Out-File @outFileParams
                } elseif ($OutputFormat -in "CSV") {
                    $outFileParams.Add("Path", $outputPath.Path)
                    $outFileParams.Add("Delimiter", ';')
                    $outFileParams.Add("NoTypeInformation", $true)
                    $output | Export-Csv @outFileParams
                } else {
                    $Exportdata = if ($Append -and ((Get-ChildItem -Path $outputPath.Path).Length -gt 0) ) { Import-Clixml -Path $outputPath.Path -ErrorAction SilentlyContinue } else { @() }
                    $Exportdata += $output
                    $Exportdata | Export-Clixml -Path $outputPath.Path -Encoding $Encoding
                }
            }

            if ($PassThru) {
                $output | ForEach-Object { [PackageUpdate.Info]$_ }
            }
        } else {
            Write-Verbose -Message "No data were processed, nothing to output."
            "" | Out-File $outputPath.Path -Encoding $Encoding
        }

    }
}


function Get-PackageUpdateInfo {
    <#
    .SYNOPSIS
        Get info about up-to-dateness for installed modules
 
    .DESCRIPTION
        Get-PackageUpdateInfo query locally installed modules and compare them against the online versions for up-to-dateness
 
    .PARAMETER Name
        The name of the module to check
 
    .PARAMETER Repository
        The repository to check
 
    .PARAMETER ShowOnlyNeededUpdate
        This switch suppresses up-to-date modules from the output.
 
    .PARAMETER ShowToastNotification
        This switch invokes nice Windows-Toast-Notifications with release note information on modules with update needed.
 
    .PARAMETER CurrentUser
        Only look for modules in the current user profile.
        This is helpfully if you're running without admin right, which you should always do as your default work preference.
 
    .PARAMETER AllUsers
        Only look for modules in the AllUsers/system directories.
        Keep in mind, that admin rights are required to update those modules.
 
    .PARAMETER Force
        Force to query info about up-to-dateness for installed modules, even if the UpdateCheckInterval
        from last check is not expired.
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateInfo
 
        Outputs update information for all modules (currentUser and AllUsers).
        Output can look like:
 
        Name Repository VersionInstalled VersionOnline NeedUpdate Path
        ---- ---------- ---------------- ------------- ---------- ----
        PSReadline PSGallery 1.2 1.2 False C:\Program Files\WindowsPowerShell\Modules\PSReadline
        Pester PSGallery 4.4.0 4.4.2 True C:\Program Files\WindowsPowerShell\Modules\Pester
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateInfo -ShowOnlyNeededUpdate
 
        This will filter output to show only modules where NeedUpdate is True
        Output can look like:
 
        Name Repository VersionInstalled VersionOnline NeedUpdate Path
        ---- ---------- ---------------- ------------- ---------- ----
        Pester PSGallery 4.4.0 4.4.2 True C:\Program Files\WindowsPowerShell\Modules\Pester
 
    .EXAMPLE
        PS C:\> "Pester", "PSReadline" | Get-PackageUpdateInfo
 
        Pipeline is supported. This returns the infos only for the two modules "Pester", "PSReadline"
 
        This also can be done with Get-Module cmdlet:
        Get-Module "Pester", "PSReadline" | Get-PackageUpdateInfo
 
    #>

    [CmdletBinding( DefaultParameterSetName = 'DefaultSet1',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low')]
    [Alias('gpui')]
    [OutputType([PackageUpdate.Info])]
    Param (
        [Parameter(ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $Name,

        [string[]]
        $Repository,

        [switch]
        $ShowOnlyNeededUpdate,

        [switch]
        [Alias('ToastNotification', 'Notify')]
        $ShowToastNotification,

        [Parameter(ParameterSetName = 'CurrentUser')]
        [switch]
        $CurrentUser,

        [Parameter(ParameterSetName = 'AllUsers')]
        [switch]
        $AllUsers,

        [switch]
        $Force
    )

    begin {
        if ($ShowToastNotification -and (-not $script:EnableToastNotification)) {
            Write-Verbose -Message "System is not able to do Toast Notifications" -Verbose
        }

        # Doing checks if the update check against the modules is done
        $moduleSetting = Get-PackageUpdateSetting
        if (-not $Force) {
            if ($moduleSetting.LastCheck -gt $moduleSetting.LastSuccessfulCheck) {
                $effectiveCheckDate = $moduleSetting.LastCheck
            } else {
                $effectiveCheckDate = $moduleSetting.LastSuccessfulCheck
            }

            if ( ($effectiveCheckDate + $moduleSetting.UpdateCheckInterval) -ge (Get-Date) ) {
                Write-Warning -Message "Skip checking for updates on modules, due to last check happens at $($effectiveCheckDate.ToShortTimeString()) and minimum UpdateCheckInterval is set to $($moduleSetting.UpdateCheckInterval)"
                break
            }
        } else {
            Write-Verbose -Message "Force parameter specified. Bypassing checking on UpdateCheckInterval and enforcing up-to-dateness check on modules"
        }
        Set-PackageUpdateSetting -LastCheck (Get-Date)

        # Get the necessary repositories
        $getPSRepositoryParams = @{ }
        if ($Repository) { $getPSRepositoryParams.Add("Name", $Repository) }
        $psRepositories = Get-PSRepository @getPSRepositoryParams -ErrorAction Stop

    }

    process {
        if (-not $Name) { $Name = "*" }
        foreach ($nameItem in $Name) {
            # Get local module(s)
            Write-Verbose "Get local module(s): $($nameItem)"
            $getModuleParams = @{
                ListAvailable = $true
                Name          = $nameItem
                Verbose       = $false
            }
            $modulesLocal = Get-Module @getModuleParams | Where-Object RepositorySourceLocation | Sort-Object Name, Version -Descending | Group-Object Name | ForEach-Object { $_.group[0] }

            # Filtering out if switches are specified
            Write-Verbose "Do the filtering..."
            if ($CurrentUser) {
                $modulesLocal = foreach ($path in $CurrentUserModulePath) { $modulesLocal | Where-Object path -Like "$($path)*" }
            }
            if ($AllUsers) {
                $modulesLocal = foreach ($path in $AllUsersModulePath) { $modulesLocal | Where-Object path -Like "$($path)*" }
            }
            if ($Repository) {
                $modulesLocal = foreach ($psRepository in $psRepositories) { $modulesLocal | Where-Object RepositorySourceLocation -like "$($psRepository.SourceLocation)*" }
            }

            # Get available modules from online repositories
            Write-Verbose "Get available modules from online repositories"
            $modulesOnline = foreach ($moduleLocalName in $modulesLocal.Name) {
                $findModuleParams = @{
                    "Name" = $moduleLocalName
                    "Verbose" = $false
                }
                if ($Repository) { $findModuleParams["Repository"] = $Repository }
                Find-Module @findModuleParams
            }

            # Compare the version and create output
            Write-Verbose "Compare the version and create output"
            foreach ($moduleOnline in $modulesOnline) {
                $moduleLocal = $modulesLocal | Where-Object Name -like $moduleOnline.Name

                if ($moduleOnline.version -gt $moduleLocal.version) {
                    Write-Verbose "Update available for module '$($moduleOnline.Name)': local v$($moduleLocal.version) --> v$($moduleOnline.version) online"
                    $UpdateAvailable = Test-UpdateIsNeeded -ModuleLocal $moduleLocal -ModuleOnline $moduleOnline
                    #$UpdateAvailable = $true
                } elseif ($moduleOnline.version -lt $moduleLocal.version) {
                    Write-Warning "Local version for module '$($moduleOnline.Name)' is higher than online version: local v$($moduleLocal.version) <-- v$($moduleOnline.version) online"
                    $UpdateAvailable = $false
                } else {
                    Write-Verbose "The module '$($moduleOnline.Name)' is up to date (Version $($moduleLocal.version))"
                    $UpdateAvailable = $false
                }

                if ($ShowOnlyNeededUpdate -and (-not $UpdateAvailable)) { continue }

                $outputHash = [ordered]@{
                    Name             = $moduleLocal.Name
                    Repository       = ($psRepositories | Where-Object SourceLocation -like "$($moduleLocal.RepositorySourceLocation.ToString().Trim('/'))*").Name
                    VersionInstalled = $moduleLocal.version
                    VersionOnline    = $moduleOnline.version
                    NeedUpdate       = $UpdateAvailable
                    Path             = $moduleLocal.ModuleBase.Replace($moduleLocal.Version, '').trim('\')
                    ProjectUri       = $moduleOnline.ProjectUri
                    IconUri          = $moduleOnline.IconUri
                    ReleaseNotes     = $moduleOnline.ReleaseNotes
                    Author           = $moduleOnline.Author
                    PublishedDate    = $moduleOnline.PublishedDate
                    Description      = $moduleOnline.Description
                }
                $PackageUpdateInfo = New-Object -TypeName PackageUpdate.Info -Property $outputHash

                if ($script:EnableToastNotification -and $ShowToastNotification -and $PackageUpdateInfo.NeedUpdate) {
                    Show-ToastNotification -PackageUpdateInfo $PackageUpdateInfo
                }

                $PackageUpdateInfo
            }
        }
    }

    end {
        Set-PackageUpdateSetting -LastSuccessfulCheck (Get-Date)
    }
}


function Get-PackageUpdateRule {
    <#
    .SYNOPSIS
        Get rule(s) for checking and reporting on installed modules
 
    .DESCRIPTION
        This command get the existing custom rule(s) how modules are handled in reporting.
 
    .PARAMETER Id
        The Id as an identifier for the rule
 
    .PARAMETER ExcludeModuleFromChecking
        ModuleNames to exclude from update checking
 
    .PARAMETER IncludeModuleForChecking
        ModuleNames to include from update checking
        By default all modules are included.
 
        Default value is: "*"
 
    .PARAMETER IncludeDefaultRule
        Outputs the DefautRule from the setting object, in addition to the customrules
 
    .PARAMETER SettingObject
        Settings object parsed in from command Get-PackageUpdateSetting
        This is an optional parameter. By default it will use the default
        settings object from the module.
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateRule
 
        Get all the existing custom rules
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateRule -Id 1
 
        Get all the custom rule with Id 1
 
    #>

    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low', DefaultParameterSetName = "ShowAll")]
    [Alias('gpur')]
    [OutputType([PackageUpdate.ModuleRule])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ById")]
        [ValidateRange(1, [int]::MaxValue)]
        [int[]]
        $Id,

        [Parameter(ParameterSetName = "ShowAll")]
        [Alias("Include", "IncludeModule")]
        [String]
        $IncludeModuleForChecking,

        [Parameter(ParameterSetName = "ShowAll")]
        [Alias("Exclude", "ExcludeModule")]
        [String]
        $ExcludeModuleFromChecking,

        [Parameter(ParameterSetName = "ShowAll")]
        [switch]
        $IncludeDefaultRule,

        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PackageUpdate.Configuration]
        $SettingObject
    )

    begin {
    }

    process {
        # If no setting object is piped in, get the current settings
        if (-not $SettingObject) { $SettingObject = Get-PackageUpdateSetting }

        $output = @()

        if ($Id) {
            $output += $SettingObject.CustomRule | Where-Object Id -in $Id | Sort-Object -Property Id
        } else {
            if ("ExcludeModuleFromChecking" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
                $output += $SettingObject.CustomRule | Where-Object ExcludeModuleFromChecking -like $ExcludeModuleFromChecking | Sort-Object -Property Id
            } elseif ("IncludeModuleForChecking" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
                $output += $SettingObject.CustomRule | Where-Object IncludeModuleForChecking -like $IncludeModuleForChecking | Sort-Object -Property Id
            } else {
                $output += $SettingObject.CustomRule | Sort-Object -Property Id
            }
        }

        if ($IncludeDefaultRule) {
            $output += $SettingObject.DefaultRule
        }

        $output
    }

    end {
    }
}


function Get-PackageUpdateSetting {
    <#
    .SYNOPSIS
        Set behaviour settings for PackageUpdateInfo module
 
    .DESCRIPTION
        Query the basic settings for check and report on up-to-dateness information on installed modules
 
    .PARAMETER Path
        The filepath where to setting file
 
        This is optional, default path value is:
        Linux: "$HOME/.local/share/powershell/PackageUpdateInfo/PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).json")
        Windows: "$HOME\AppData\Local\Microsoft\Windows\PowerShell\PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).json")
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateSetting
 
        Get the current settings on PackageUpdateInfo behaviour.
 
    #>

    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    [Alias('gpus')]
    [OutputType([PackageUpdate.Configuration])]
    Param (
        [Parameter(Position = 0)]
        [Alias("FullName", "FilePath")]
        [String]
        $Path = $script:ModuleSettingPath
    )

    begin {
    }

    process {
        # read settings file
        try {
            $configuration = Get-Content -Path $Path -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
        } catch {
            Write-Warning -Message "Module configuration file not found! ($($Path))"
            Write-Warning -Message "Please check the path or initialize configuration by using 'Set-PackageUpdateSetting -Reset'"
            throw
        }

        # Initialize setting object and fill in values
        $output = New-Object -TypeName PackageUpdate.Configuration

        $output.CustomRule = foreach ($rule in $configuration.CustomRule) {
            [PackageUpdate.ModuleRule]@{
                Id                        = $rule.Id
                ExcludeModuleFromChecking = $rule.ExcludeModuleFromChecking
                IncludeModuleForChecking  = $rule.IncludeModuleForChecking
                ReportChangeOnBuild       = $rule.ReportChangeOnBuild
                ReportChangeOnMajor       = $rule.ReportChangeOnMajor
                ReportChangeOnMinor       = $rule.ReportChangeOnMinor
                ReportChangeOnRevision    = $rule.ReportChangeOnRevision
            }
        }

        $output.DefaultRule = [PackageUpdate.ModuleRule]@{
            ExcludeModuleFromChecking = $configuration.DefaultRule.ExcludeModuleFromChecking
            IncludeModuleForChecking  = $configuration.DefaultRule.IncludeModuleForChecking
            ReportChangeOnBuild       = $configuration.DefaultRule.ReportChangeOnBuild
            ReportChangeOnMajor       = $configuration.DefaultRule.ReportChangeOnMajor
            ReportChangeOnMinor       = $configuration.DefaultRule.ReportChangeOnMinor
            ReportChangeOnRevision    = $configuration.DefaultRule.ReportChangeOnRevision
        }

        if ("System.TimeSpan" -in $configuration.UpdateCheckInterval.psobject.TypeNames) {
            $output.UpdateCheckInterval = [timespan]::new($configuration.UpdateCheckInterval.Days, $configuration.UpdateCheckInterval.Hours, $configuration.UpdateCheckInterval.Minutes, $configuration.UpdateCheckInterval.Seconds, $configuration.UpdateCheckInterval.Milliseconds)
        } else {
            $output.UpdateCheckInterval = [timespan]$configuration.UpdateCheckInterval
        }

        if ($configuration.LastCheck) {
            $output.LastCheck = [datetime]$configuration.LastCheck
        } else {
            $output.LastCheck = [datetime]::MinValue
        }
        if ($configuration.LastSuccessfulCheck) {
            $output.LastSuccessfulCheck = [datetime]$configuration.LastSuccessfulCheck
        } else {
            $output.LastSuccessfulCheck = [datetime]::MinValue
        }

        $output.Path = $configuration.Path

        $output
    }

    end {
    }
}


function Import-PackageUpdateInfo {
    <#
    .SYNOPSIS
        Import PackageUpdateInfo from a data file
 
    .DESCRIPTION
        Import PackageUpdateInfo from a data file previously exported with funtion Export-PackageUpdateInfo.
 
    .PARAMETER Path
        The filepath where to import the informations.
        Please specify a file as path.
 
        Default path value is:
        Linux: "$HOME/.local/share/powershell/PackageUpdateInfo/PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml")
        Windows: "$HOME\AppData\Local\Microsoft\Windows\PowerShell\PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml")
 
    .PARAMETER ShowToastNotification
        This switch invokes nice Windows-Toast-Notifications with release note information on modules with update needed.
 
    .PARAMETER InputFormat
        The output format for the data
        Available formats are "XML","JSON","CSV"
 
    .PARAMETER Encoding
        File Encoding for the file
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Import-PackageUpdateInfo
 
        Try to import the default file "$HOME\AppData\Local\Microsoft\Windows\PowerShell\PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml"
    #>

    [CmdletBinding( SupportsShouldProcess = $true,
        ConfirmImpact = 'Low')]
    [Alias('ipui')]
    [OutputType([PackageUpdate.Info])]
    Param (
        [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("FullName", "FilePath")]
        [String]
        $Path,

        [switch]
        [Alias('ToastNotification', 'Notify')]
        $ShowToastNotification,

        [ValidateSet("XML", "JSON", "CSV")]
        [Alias("Format")]
        [String]
        $InputFormat = "XML",

        [ValidateSet("default", "utf7", "utf8", "utf32", "unicode", "ascii", "string", "oem", "bigendianunicode")]
        [String]
        $Encoding = "default"
    )

    begin {
        if($ShowToastNotification -and (-not $script:EnableToastNotification)) {
            Write-Verbose -Message "System is not able to do Toast Notifications" -Verbose
        }

        # Set path variable to default value, when not specified
        if(-not $path) {
            if($IsLinux) {
                $path = (Join-Path $HOME ".local/share/powershell/PackageUpdateInfo/PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml")
            } else {
                $path = (Join-Path $HOME "AppData\Local\Microsoft\Windows\PowerShell\PackageUpdateInfo_$($PSEdition)_$($PSVersionTable.PSVersion.Major).xml")
            }
        }
    }

    process {
        $file = Get-ChildItem -Path $Path -ErrorAction Stop

        if ($pscmdlet.ShouldProcess($Path, "Import PackageUpdateInfo")) {
            if ($file.Length -gt 2) {
                Write-Verbose "Importing package update information from $($file.FullName)"

                if ($InputFormat -like "JSON") {
                    $records = Get-Content -Path $file -Encoding $Encoding | ConvertFrom-Json -ErrorAction SilentlyContinue
                } elseif ($InputFormat -like "CSV") {
                    $records = Import-Csv -Path $file -Delimiter ";" -Encoding $Encoding
                } else {
                    $records = Import-Clixml -Path $file
                }

                foreach ($record in $records) {
                    $_date = $record.PublishedDate | Get-Date
                    $hash = [ordered]@{
                        Name             = $record.Name
                        Repository       = $record.Repository
                        VersionInstalled = [version]$record.VersionInstalled
                        VersionOnline    = [version]$record.VersionOnline
                        NeedUpdate       = [bool]::Parse($record.NeedUpdate)
                        Path             = $record.Path
                        ProjectUri       = $record.ProjectUri
                        IconUri          = $record.IconUri
                        ReleaseNotes     = $record.ReleaseNotes
                        Author           = $record.Author
                        PublishedDate    = $_date
                        Description      = $record.Description
                    }
                    $PackageUpdateInfo = [PackageUpdate.Info]$hash
                    if ($script:EnableToastNotification -and $ShowToastNotification -and $PackageUpdateInfo.NeedUpdate) { Show-ToastNotification -PackageUpdateInfo $PackageUpdateInfo }
                    $PackageUpdateInfo
                }
            } else {
                Write-Verbose "Nothing to import. Seems like, all is up to date."
            }
        }
    }

    end {
    }
}


function Remove-PackageUpdateRule {
    <#
    .SYNOPSIS
        remove rule(s) for checking and reporting on installed modules
 
    .DESCRIPTION
        This command remove existing custom rule(s) on how a modules is handled in reporting.
 
    .PARAMETER Id
        The Id of the rule to be removed
 
    .PARAMETER InputObject
        Settings object parsed in from command Get-PackageUpdateSetting
        This is an optional parameter
 
    .PARAMETER Force
        If specified the user will not prompted on confirmation.
 
    .PARAMETER PassThru
        The rule object will be parsed to the pipeline for further processing
 
    .PARAMETER SettingObject
        Settings object from command Get-PackageUpdateSetting
        This is an optional parameter. By default it will use the default
        settings object from the module.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateRule | Remove-PackageUpdateRule
 
        Remove all custom rules for module update handling.
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = "ById")]
    [Alias('rpur')]
    [OutputType([PackageUpdate.ModuleRule])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ById")]
        [ValidateRange(1, [int]::MaxValue)]
        [int[]]
        $Id,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ByInputObject")]
        [PackageUpdate.ModuleRule[]]
        $InputObject,

        [switch]
        $Force,

        [switch]
        $PassThru,

        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PackageUpdate.Configuration]
        $SettingObject
    )

    begin {
    }

    process {
        # If no setting object is piped in, get the current settings
        if (-not $SettingObject) { $SettingObject = Get-PackageUpdateSetting }

        # Find the rule by Id
        if ($id) { $InputObject = Get-PackageUpdateRule -Id $id }

        # Remove the rule(s) from the setting object
        foreach ($rule in $InputObject) {
            Write-Verbose -Message "Remove rule Id $($rule.Id)"
            $SettingObject.CustomRule = $SettingObject.CustomRule | Where-Object Id -ne $rule.id
        }

        # Write back the configuration object
        if ($Force) { $doAction = $true } else { $doAction = $pscmdlet.ShouldProcess("Rule Id $([string]::Join(", ", $rule.id))", "Remove from PackUpdateSetting object ($($SettingObject.Path))") }
        if ($doAction) {
            $SettingObject | ConvertFrom-PackageUpdateSetting | ConvertTo-Json | Out-File -FilePath $SettingObject.Path -Encoding default -Force
        }

        # Output the object if PassThru is specified
        if ($PassThru) { $InputObject }
    }

    end {
    }
}


function Set-PackageUpdateRule {
    <#
    .SYNOPSIS
        Set a rule for checking and reporting on installed modules
 
    .DESCRIPTION
        This command allows to edit existing rules on how a modules is handled in reporting.
 
        For example, you can configure PackageUpdateINfo to suppress revision updates on a frequent
        updated module, so that only build, minor or major updates are reportet as "update needed".
 
    .PARAMETER Id
        The Id as an identifier for the rule
 
    .PARAMETER InputObject
        The rule object to modify
 
    .PARAMETER ExcludeModuleFromChecking
        ModuleNames to exclude from update checking
 
    .PARAMETER IncludeModuleForChecking
        ModuleNames to include from update checking
        By default all modules are included.
 
        Default value is: "*"
 
    .PARAMETER ReportChangeOnMajor
        Report when major version changed for a module
 
        This means 'Get-PackageUpdateSetting' report update need,
        only when the major version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        1 0 0 0
 
    .PARAMETER ReportChangeOnMinor
        Report when minor version changed for a module
 
        This means 'Get-PackageUpdateSetting' report update need,
        only when the minor version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        0 1 0 0
 
    .PARAMETER ReportChangeOnBuild
        Report when build version changed for a module
 
        This means 'Get-PackageUpdateSetting' report update need,
        when the build version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        0 0 1 0
 
    .PARAMETER ReportChangeOnRevision
        Report when revision part changed for a module
 
        This means 'Get-PackageUpdateSetting' report update need,
        when the revision version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        1 0 0 0
 
    .PARAMETER SettingObject
        Settings object parsed in from command Get-PackageUpdateSetting
        This is an optional parameter. By default it will use the default
        settings object from the module.
 
    .PARAMETER PassThru
        The rule object will be parsed to the pipeline for further processing
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Add-PackageUpdateRule -IncludeModuleForChecking "MyModule" -ReportChangeOnMajor $true -ReportChangeOnMinor $true -ReportChangeOnBuild $true -ReportChangeOnRevision $false
 
        Add a new custom rule for "MyModule" to supress notifications on revision updates of the module
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = "ById")]
    [Alias('spur')]
    [OutputType([PackageUpdate.ModuleRule])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ById")]
        [ValidateRange(1, [int]::MaxValue)]
        [int]
        $Id,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ByInputObject")]
        [PackageUpdate.ModuleRule[]]
        $InputObject,

        [Alias("Include", "IncludeModule")]
        [String[]]
        $IncludeModuleForChecking,

        [Alias("Exclude", "ExcludeModule")]
        [AllowEmptyString()]
        [String[]]
        $ExcludeModuleFromChecking,

        [bool]
        $ReportChangeOnMajor,

        [bool]
        $ReportChangeOnMinor,

        [bool]
        $ReportChangeOnBuild,

        [bool]
        $ReportChangeOnRevision,

        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [PackageUpdate.Configuration]
        $SettingObject,

        [switch]
        $PassThru
    )

    begin {
    }

    process {
        # If no setting object is piped in, get the current settings
        if (-not $SettingObject) { $SettingObject = Get-PackageUpdateSetting }

        # Find the rule by Id
        if ($id) { $InputObject = Get-PackageUpdateRule -Id $id }

        # Work through all objects
        foreach ($rule in $InputObject) {
            # Set the new preference values
            if ("ExcludeModuleFromChecking" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
                Write-Verbose "Setting ExcludeModuleFromChecking: '$([string]::Join(", ", $ExcludeModuleFromChecking))'"
                $rule.ExcludeModuleFromChecking = $ExcludeModuleFromChecking
            }
            if ("IncludeModuleForChecking" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
                Write-Verbose "Setting IncludeModuleForChecking: '$([string]::Join(", ", $IncludeModuleForChecking))'"
                $rule.IncludeModuleForChecking = $IncludeModuleForChecking
            }
            if ("ReportChangeOnMajor" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
                Write-Verbose "Setting ReportChangeOnMajor: $($ReportChangeOnMajor)"
                $rule.ReportChangeOnMajor = $ReportChangeOnMajor
            }
            if ("ReportChangeOnMinor" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
                Write-Verbose "Setting ReportChangeOnMinor: $($ReportChangeOnMinor)"
                $rule.ReportChangeOnMinor = $ReportChangeOnMinor
            }
            if ("ReportChangeOnBuild" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
                Write-Verbose "Setting ReportChangeOnBuild: $($ReportChangeOnBuild)"
                $rule.ReportChangeOnBuild = $ReportChangeOnBuild
            }
            if ("ReportChangeOnRevision" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) {
                Write-Verbose "Setting ReportChangeOnRevision: $($ReportChangeOnRevision)"
                $rule.ReportChangeOnRevision = $ReportChangeOnRevision
            }

            # change rule and write back PackageUpdateSetting object
            if ($pscmdlet.ShouldProcess("Rule Id $($rule.id)", "Set properties from PackUpdateSetting object ($($SettingObject.Path))")) {
                $SettingObject.CustomRule = $SettingObject.CustomRule | Where-Object Id -ne $rule.Id | Sort-Object -Property Id
                $SettingObject.CustomRule += $rule
                $SettingObject.CustomRule = $SettingObject.CustomRule | Sort-Object -Property Id

                $SettingObject | ConvertFrom-PackageUpdateSetting | ConvertTo-Json | Out-File -FilePath $SettingObject.Path -Encoding default -Force
            }

            if ($PassThru) { $rule }
        }
    }

    end {
    }
}


function Set-PackageUpdateSetting {
    <#
    .SYNOPSIS
        Set behaviour settings for PackageUpdateInfo module
 
    .DESCRIPTION
        Set-PackageUpdateInfo configure basic settings for check and report on up-to-dateness information on installed modules
 
    .PARAMETER Path
        The filepath where to setting file is stored
 
        This is optional, default path value is:
        Linux: "$HOME/.local/share/powershell/PackageUpdateInfo/PackageUpdateSetting_$($PSEdition)_$($PSVersionTable.PSVersion.Major).json")
        Windows: "$HOME\AppData\Local\Microsoft\Windows\PowerShell\PackageUpdateSetting_$($PSEdition)_$($PSVersionTable.PSVersion.Major).json")
 
    .PARAMETER InputObject
        Settings object parsed in from command Get-PackageUpdateSetting
 
    .PARAMETER ExcludeModuleFromChecking
        ModuleNames to exclude from update checking in the default rule
 
    .PARAMETER IncludeModuleForChecking
        ModuleNames to include from update checking in the default rule
        By default all modules are included.
 
        Default value is: "*"
 
    .PARAMETER ReportChangeOnMajor
        Report when major version changed for a module in the default rule
 
        This means 'Get-PackageUpdateSetting' report update need,
        only when the major version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        1 0 0 0
 
    .PARAMETER ReportChangeOnMinor
        Report when minor version changed for a module in the default rule
 
        This means 'Get-PackageUpdateSetting' report update need,
        only when the minor version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        0 1 0 0
 
    .PARAMETER ReportChangeOnBuild
        Report when build version changed for a module in the default rule
 
        This means 'Get-PackageUpdateSetting' report update need,
        when the build version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        0 0 1 0
 
    .PARAMETER ReportChangeOnRevision
        Report when revision part changed for a module in the default rule
 
        This means 'Get-PackageUpdateSetting' report update need,
        when the revision version version of a module change.
 
        Major Minor Build Revision
        ----- ----- ----- --------
        1 0 0 0
 
    .PARAMETER UpdateCheckInterval
        The minimum interval/timespan has to gone by,for doing a new module update check
 
        Default value is: "01:00:00"
 
    .PARAMETER LastCheck
        Timestamp when last check for update need on modules started
 
    .PARAMETER LastSuccessfulCheck
        Timestamp when last check for update need finished
 
    .PARAMETER Reset
        Reset module to it'S default behaviour
 
    .PARAMETER PassThru
        The setting object will be parsed to the pipeline for further processing
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Set-PackageUpdateSetting -ExcludeModuleFromChecking @("") -IncludeModuleForChecking "*" -ReportChangeOnMajor $true -ReportChangeOnMinor $true -ReportChangeOnBuild $true -ReportChangeOnRevision $true -UpdateCheckInterval "01:00:00"
 
        Reset module to it'S default behaviour
 
    .EXAMPLE
        PS C:\> Set-PackageUpdateSetting -Reset
 
        Reset module to it'S default behaviour
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [Alias('spus')]
    [OutputType([PackageUpdate.Configuration])]
    Param (
        [Parameter(ParameterSetName = "SetBehaviour")]
        [Alias("Exclude", "ExcludeModule")]
        [AllowEmptyString()]
        [String[]]
        $ExcludeModuleFromChecking,

        [Parameter(ParameterSetName = "SetBehaviour")]
        [Alias("Include", "IncludeModule")]
        [String[]]
        $IncludeModuleForChecking,

        [Parameter(ParameterSetName = "SetBehaviour")]
        [bool]
        $ReportChangeOnMajor,

        [Parameter(ParameterSetName = "SetBehaviour")]
        [bool]
        $ReportChangeOnMinor,

        [Parameter(ParameterSetName = "SetBehaviour")]
        [bool]
        $ReportChangeOnBuild,

        [Parameter(ParameterSetName = "SetBehaviour")]
        [bool]
        $ReportChangeOnRevision,

        [Parameter(ParameterSetName = "SetBehaviour")]
        [timespan]
        $UpdateCheckInterval,

        [Parameter(ParameterSetName = "SetBehaviour")]
        [datetime]
        $LastCheck,

        [Parameter(ParameterSetName = "SetBehaviour")]
        [datetime]
        $LastSuccessfulCheck,

        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "SetBehaviour")]
        [PackageUpdate.Configuration]
        $InputObject,

        [Parameter(ParameterSetName = "ResetBehaviour")]
        [switch]
        $Reset,

        [Alias("FullName", "FilePath")]
        [String]
        $Path,

        [switch]
        $PassThru
    )

    begin {
    }

    process {
        if ($PSCmdlet.ParameterSetName -like "ResetBehaviour") {
            if (-not $Path) { $Path = $script:ModuleSettingPath }

            if ($pscmdlet.ShouldProcess($path, "Reset PackageUpdateInfo behaviour")) {
                # Initialize default preferences
                $defaultSetting = [PackageUpdate.Configuration]@{
                    CustomRule          = @()
                    DefaultRule         = [PackageUpdate.ModuleRule]@{
                        ExcludeModuleFromChecking = @("")
                        IncludeModuleForChecking  = @("*")
                        ReportChangeOnMajor       = $true
                        ReportChangeOnMinor       = $true
                        ReportChangeOnBuild       = $true
                        ReportChangeOnRevision    = $true
                    }
                    UpdateCheckInterval = "01:00:00"
                    LastCheck           = [string][datetime]::MinValue
                    LastSuccessfulCheck = [string][datetime]::MinValue
                    Path                = $Path
                }

                # Write setting to file
                $defaultSetting | ConvertFrom-PackageUpdateSetting | ConvertTo-Json | Out-File -FilePath $Path -Encoding default -Force

                if ($PassThru) {
                    $defaultSetting
                }
            }
        }

        if ($PSCmdlet.ParameterSetName -like "SetBehaviour") {
            # If no setting object is piped in, get the current settings
            if (-not $InputObject) {
                $paramPackageUpdateSetting = @{ }
                if ($Path) { $paramPackageUpdateSetting["Path"] = $Path }
                $InputObject = Get-PackageUpdateSetting @paramPackageUpdateSetting
            }

            # check if path is specified
            if ($Path) {
                if (-not ($Path -like $InputObject.Path)) {
                    Write-Verbose -Message "Setting object is piped in but a different path for PackageUpdate setting is specified. Piped in object will override path setting. (Effective path: $($InputObject.Path))" -Verbose
                    $Path = $InputObject.Path
                }
            } else {
                $Path = $InputObject.Path
            }

            # Set the new preference values
            if ("ExcludeModuleFromChecking" -in $PSBoundParameters.Keys) {
                Write-Verbose "Setting ExcludeModuleFromChecking: '$([string]::Join(", ", $ExcludeModuleFromChecking))'"
                $InputObject.DefaultRule.ExcludeModuleFromChecking = $ExcludeModuleFromChecking
            }
            if ("IncludeModuleForChecking" -in $PSBoundParameters.Keys) {
                Write-Verbose "Setting IncludeModuleForChecking: '$([string]::Join(", ", $IncludeModuleForChecking))'"
                $InputObject.DefaultRule.IncludeModuleForChecking = $IncludeModuleForChecking
            }
            if ($PSBoundParameters["ReportChangeOnMajor"]) {
                Write-Verbose "Setting ReportChangeOnMajor: $($ReportChangeOnMajor)"
                $InputObject.DefaultRule.ReportChangeOnMajor = $ReportChangeOnMajor
            }
            if ($PSBoundParameters["ReportChangeOnMinor"]) {
                Write-Verbose "Setting ReportChangeOnMinor: $($ReportChangeOnMinor)"
                $InputObject.DefaultRule.ReportChangeOnMinor = $ReportChangeOnMinor
            }
            if ($PSBoundParameters["ReportChangeOnBuild"]) {
                Write-Verbose "Setting ReportChangeOnBuild: $($ReportChangeOnBuild)"
                $InputObject.DefaultRule.ReportChangeOnBuild = $ReportChangeOnBuild
            }
            if ($PSBoundParameters["ReportChangeOnRevision"]) {
                Write-Verbose "Setting ReportChangeOnRevision: $($ReportChangeOnRevision)"
                $InputObject.DefaultRule.ReportChangeOnRevision = $ReportChangeOnRevision
            }
            if ($PSBoundParameters["UpdateCheckInterval"]) {
                Write-Verbose "Setting UpdateCheckInterval: $($UpdateCheckInterval)"
                $InputObject.UpdateCheckInterval = $UpdateCheckInterval
            }
            if ($PSBoundParameters["LastCheck"]) {
                Write-Verbose "Setting LastCheck: $($LastCheck)"
                $InputObject.LastCheck = $LastCheck
            }
            if ($PSBoundParameters["LastSuccessfulCheck"]) {
                Write-Verbose "Setting LastSuccessfulCheck: '$($LastSuccessfulCheck)'"
                $InputObject.LastSuccessfulCheck = $LastSuccessfulCheck
            }
            Write-Verbose "Setting 'Path': $($Path)"
            $InputObject.Path = $Path

            if ($pscmdlet.ShouldProcess($path, "Export PackageUpdateInfo")) {
                $InputObject | ConvertFrom-PackageUpdateSetting | ConvertTo-Json | Out-File -FilePath $Path -Encoding default -Force
            }
        }
    }

    end {
    }
}


function Show-PackageUpdateReleaseNote {
    <#
    .SYNOPSIS
        Show release notes from a module
 
    .DESCRIPTION
        Show release notes from a module.
 
    .PARAMETER InputObject
        Input object(s) from Get-PackageUpdateInfo or Import-PackageUpdateInfo to show release notes
 
    .PARAMETER Module
        Input object(s) from Get-Module to show release notes
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Get-PackageUpdateInfo | Show-PackageUpdateReleaseNote
 
        Get release notes out of PackageUpdateInfo objects
 
    .EXAMPLE
        PS C:\> Get-Module PackageUpdateInfo | Show-PackageUpdateReleaseNote
 
        Get relase notes from a module
 
    #>

    [CmdletBinding( SupportsShouldProcess = $true,
        ConfirmImpact = 'Low')]
    [Alias('spurn')]
    [OutputType([PackageUpdate.ReleaseNote])]
    Param (
        [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByPackageUpdeInfoObject')]
        [Alias("Input")]
        [PackageUpdate.Info[]]
        $InputObject,

        [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByModuleObject')]
        [System.Management.Automation.PSModuleInfo[]]
        [Alias('ModuleName')]
        $Module
    )

    begin {
    }

    process {
        $modulesToProcess = @()

        # Process input pipeline for PackageUpdateInfo objects
        $modulesToProcess += foreach ($inputObjectItem in $InputObject) {
            if ($inputObjectItem.HasReleaseNotes) {
                [PackageUpdate.ReleaseNote]@{
                    Name         = $inputObjectItem.Name
                    Version      = $inputObjectItem.VersionOnline
                    ReleaseNotes = $inputObjectItem.ReleaseNotes
                }
            } else {
                Write-Verbose "No release notes found for module $($inputObjectItem.Name) ($($inputObjectItem.Version))" -Verbose
            }
        }

        # Process input pipeline for modules
        $modulesToProcess += foreach ($moduleItem in $Module) {
            if ($moduleItem.ReleaseNotes) {
                [PackageUpdate.ReleaseNote]@{
                    Name         = $moduleItem.Name
                    Version      = $moduleItem.Version
                    ReleaseNotes = $moduleItem.ReleaseNotes
                }
            } else {
                Write-Verbose "No release notes found for module $($moduleItem.Name) ($($moduleItem.Version))" -Verbose
            }
        }

        # Working through the module objects
        foreach ($item in $modulesToProcess) {
            if ($pscmdlet.ShouldProcess($item.Name, "Show release notes")) {

                # If release notes are an URL, try to gather release notes from web site
                if ($item.ReleaseNotesIsURI) {
                    # Set basic variables
                    $msgUnableToGather = "Unable to gather release notes from website. Please use a browser to visit: $($item.ReleaseNotesURI)"
                    $paramInvokeWebRequest = @{
                        TimeoutSec  = 2
                        ErrorAction = "SilentlyContinue"
                    }

                    # Gathering release notes from web modules is only possible in some certain cases
                    # if url points to a file on a github repo, it is possible to get plain text information
                    # in other cases, the url will be tried to resolve and double check if it is a github repo (quicklinks/ forwardings)
                    switch ($item.ReleaseNotesURI) {
                        { Assert-PossibleReleaseNotesURI -URI $_ } {
                            if ($item.ReleaseNotesURI -like "*/releases/*") {
                                # Github release pages can't be covered. Set unable-message
                                $item.Notes = $msgUnableToGather

                            } else {
                                # Set variables for Invoke-WebRequest
                                $paramInvokeWebRequest["Uri"] = $item.ReleaseNotesURI.ToString() -replace "/blob/", "/raw/"
                                if ($PSVersionTable.PSEdition -like "Desktop") { $paramInvokeWebRequest["UseBasicParsing"] = $true }

                                # Gather release notes from web
                                $webData = Invoke-WebRequest @paramInvokeWebRequest

                                # Set web content as notes
                                $item.Notes = $webData.Content

                            }
                        }

                        default {
                            # Default case. URL seams not to be a known/parseable URL for plain text release notes.
                            # gather the URL anyway to check if it is a forwarding/shortlink URL to github

                            # Set variables for Invoke-WebRequest to resolve the specified URI
                            $paramInvokeWebRequest["Uri"] = $item.ReleaseNotesURI.ToString()
                            if ($PSVersionTable.PSEdition -like "Desktop") { $paramInvokeWebRequest["UseBasicParsing"] = $true }

                            # Gather URI from web
                            $webData = Invoke-WebRequest @paramInvokeWebRequest

                            # Extract URI from WebRequest response
                            if ($PSVersionTable.PSEdition -like "Desktop") {
                                $_uri = $webData.BaseResponse.ResponseUri
                            } else {
                                $_uri = $webData.BaseResponse.RequestMessage.RequestUri
                            }

                            # Is gathered WebRequest a "valid" URI, now?
                            if (Assert-PossibleReleaseNotesURI -URI $_uri) {
                                # Set variables for final Invoke-WebRequest to resolve the plain text release notes
                                $paramInvokeWebRequest.uri = $_uri -replace "/blob/", "/raw/"

                                # Gather release notes from web
                                $webData = Invoke-WebRequest @paramInvokeWebRequest

                                # set web content as notes
                                $item.Notes = $webData.Content

                            } else {
                                # Set unable-message
                                $item.Notes = $msgUnableToGather

                            }
                        }
                    }
                }

                # Output result item
                $item
            }
        }
    }

    end {
    }
}


# Place all code that should be run after functions are imported here

$script:ModuleIconPath = Join-Path -Path $script:ModuleRoot -ChildPath "\bin\PackageUpdateInfo.png"

if ($isLinux) {
    $script:ModuleTempPath = Join-Path -Path "/tmp" -ChildPath "PackageUpdateInfo"
    $script:ModuleSettingPath = Join-Path -Path $HOME -ChildPath ".local/share/powershell/PackageUpdateInfo/PackageUpdateSetting_$($PSEdition)_$($PSVersionTable.PSVersion.Major).json"
} else {
    $script:ModuleTempPath = Join-Path -Path $env:TEMP -ChildPath "PackageUpdateInfo"
    $script:ModuleSettingPath = Join-Path -Path $HOME -ChildPath "AppData\Local\Microsoft\Windows\PowerShell\PackageUpdateSetting_$($PSEdition)_$($PSVersionTable.PSVersion.Major).json"
}

$script:CurrentUserModulePath = $env:PSModulePath.split(';') | Where-Object { $_ -like "$(Split-Path $PROFILE -Parent)*" -or $_ -like "$($HOME)*" }
$script:AllUsersModulePath = $env:PSModulePath.split(';') | Where-Object { $_ -notlike "$(Split-Path $PROFILE -Parent)*" -and $_ -notlike "$($HOME)*" }

if (Test-Path -Path $script:ModuleTempPath) {
    Get-ChildItem -Path $script:ModuleTempPath | Where-Object LastWriteTime -lt (Get-Date).AddHours(-2) | Remove-Item -Force -Confirm:$false
} else {
    New-Item -Path $script:ModuleTempPath -ItemType Directory -Force | Out-Null
}

if (-not (Test-Path -Path $script:ModuleSettingPath)) {
    Write-Verbose -Message "Going to initialize default settings for module PackageUpdateInfo"
    Set-PackageUpdateSetting -Reset -Path $script:ModuleSettingPath
}

#endregion Load compiled code