diff --git a/GitHubConfiguration.ps1 b/GitHubConfiguration.ps1 index 604fea5e..0d6e9f21 100644 --- a/GitHubConfiguration.ps1 +++ b/GitHubConfiguration.ps1 @@ -110,6 +110,10 @@ function Set-GitHubConfiguration Specify this switch to stop the module from reporting any of its usage (which would be used for diagnostics purposes). + .PARAMETER DisableUpdateCheck + Specify this switch to stop the daily update check with PowerShellGallery which can + inform you when there is a newer version of this module available. + .PARAMETER LogPath The location of the log file that all activity will be written to if DisableLogging remains $false. @@ -194,6 +198,8 @@ function Set-GitHubConfiguration [switch] $DisableTelemetry, + [switch] $DisableUpdateCheck, + [string] $LogPath, [switch] $LogProcessId, @@ -281,6 +287,7 @@ function Get-GitHubConfiguration 'DisablePiiProtection', 'DisableSmarterObjects', 'DisableTelemetry', + 'DisableUpdateCheck', 'LogPath', 'LogProcessId', 'LogRequestBody', @@ -615,6 +622,7 @@ function Import-GitHubConfiguration 'disablePiiProtection' = $false 'disableSmarterObjects' = $false 'disableTelemetry' = $false + 'disableUpdateCheck' = $false 'defaultNoStatus' = $false 'defaultOwnerName' = [String]::Empty 'defaultRepositoryName' = [String]::Empty diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 59acd6f4..7a5850c6 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -129,6 +129,8 @@ function Invoke-GHRestMethod [switch] $NoStatus ) + Invoke-UpdateCheck + # Normalize our Uri fragment. It might be coming from a method implemented here, or it might # be coming from the Location header in a previous response. Either way, we don't want there # to be a leading "/" or trailing '/' diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 7bcec540..02be20c7 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -43,7 +43,8 @@ 'GitHubTeams.ps1', 'GitHubUsers.ps1', 'NugetTools.ps1', - 'Telemetry.ps1') + 'Telemetry.ps1', + 'UpdateCheck.ps1') # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '4.0' diff --git a/README.md b/README.md index 15758273..4cedbeb4 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ access token. If you ever wish to clear it in the future, just call `Clear-GitHubAuthentication`). > For automated scenarios (like GithHub Actions) where you are dynamically getting the access token -> needed for authentication, refer to `Example 2` in `Get-Help Set-StoreBrokerAuthentication -Examples` +> needed for authentication, refer to `Example 2` in `Get-Help Set-GitHubAuthentication -Examples` > for how to configure in a promptless fashion. > > Alternatively, you _could_ configure PowerShell itself to always pass in a plain-text access token diff --git a/UpdateCheck.ps1 b/UpdateCheck.ps1 new file mode 100644 index 00000000..b2ec5099 --- /dev/null +++ b/UpdateCheck.ps1 @@ -0,0 +1,145 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# We'll cache our job name and result so that we don't check more than once per day. +$script:UpdateCheckJobName = $null +$script:HasLatestVersion = $null + +function Invoke-UpdateCheck +{ +<# + .SYNOPSIS + Checks PowerShellGallery to see if a newer version of this module has been published. + + .DESCRIPTION + Checks PowerShellGallery to see if a newer version of this module has been published. + + The check will only run once per day. + + Runs asynchronously, so the user won't see any message until after they run their first + API request after the module has been imported. + + Will always assume true in the event of an incomplete or failed check. + + Reports the result to the user via a Warning message (if a newer version is available) + or a Verbose message (if they're running the latest version or the version check failed). + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .EXAMPLE + Invoke-UpdateCheck + + .NOTES + Internal-only helper method. +#> + param() + + if (Get-GitHubConfiguration -Name DisableUpdateCheck) + { + return + } + + $moduleName = $MyInvocation.MyCommand.Module.Name + $moduleVersion = $MyInvocation.MyCommand.Module.Version + + $jobNameToday = "Invoke-UpdateCheck-" + (Get-Date -format 'yyyyMMdd') + + # We only check once per day + if ($jobNameToday -eq $script:UpdateCheckJobName) + { + # Have we retrieved the status yet? $null means we haven't. + if ($null -ne $script:HasLatestVersion) + { + # We've already completed the check for today. No further action required. + return + } + + $state = (Get-Job -Name $script:UpdateCheckJobName).state + if ($state -eq 'Failed') + { + # We'll just assume we're up-to-date if we failed to check today. + Write-Log -Message '[$moduleName] update check failed for today (web request failed). Assuming up-to-date.' -Level Verbose + $script:HasLatestVersion = $true + + # Clear out the job info (even though we don't need the result) + $null = Receive-Job -Name $script:UpdateCheckJobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable errorInfo + return + } + elseif ($state -eq 'Completed') + { + $result = Receive-Job -Name $script:UpdateCheckJobName -AutoRemoveJob -Wait + try + { + # Occasionally the API puts two nearly identical XML responses in the body (each on a separate line). + # We'll try to avoid an unnecessary failure by always using the first line of the response. + $xml = [xml]($result.Content.Split([Environment]::NewLine) | Select-Object -First 1) + $latestVersion = $xml.feed.entry.properties.version | + ForEach-Object {[System.Version]$_} | + Sort-Object -Descending | + Select-Object -First 1 + + $script:HasLatestVersion = $latestVersion -eq $moduleVersion + if ($script:HasLatestVersion) + { + Write-Log "[$moduleName] update check complete. Running latest version: $($latestVersion.ToString())" -Level Verbose + } + else + { + Write-Log "A newer version of $moduleName is available ($latestVersion). Your current version is $moduleVersion. Run 'Update-Module $moduleName' to get up-to-date." -Level Warning + } + } + catch + { + # This could happen if the server returned back an invalid (non-XML) response for the request + # for some reason. + Write-Log -Message "[$moduleName] update check failed for today (invalid XML response). Assuming up-to-date." -Level Verbose + $script:HasLatestVersion = $true + } + + return + } + else + { + # It's still running. Nothing further for us to do right now. We'll check back + # again next time. + return + } + } + else + { + # We either haven't checked yet, or it's a new day so we should check again. + $script:UpdateCheckJobName = $jobNameToday + $script:HasLatestVersion = $null + } + + [scriptblock]$scriptBlock = { + param($ModuleName) + + $params = @{} + $params.Add('Uri', "https://www.powershellgallery.com/api/v2/FindPackagesById()?id='$ModuleName'") + $params.Add('Method', 'Get') + $params.Add('UseDefaultCredentials', $true) + $params.Add('UseBasicParsing', $true) + + try + { + Invoke-WebRequest @params + } + catch + { + # We will silently ignore any errors that occurred, but we need to make sure that + # we are explicitly catching and throwing them so that our reported state is Failed. + throw + } + } + + $null = Start-Job -Name $script:UpdateCheckJobName -ScriptBlock $scriptBlock -Arg @($moduleName) + + # We're letting this run asynchronously so that users can immediately start using the module. + # We'll check back in on the result of this the next time they run any command. +} + +# We will explicitly run this as soon as the module has been loaded, +# and then it will be called again during every major function call in the module +# so that the result can be reported. +Invoke-UpdateCheck \ No newline at end of file