Skip to content

Commit a9f48a8

Browse files
Add automatic daily update checking (#185)
The module will now check daily with PowerShellGallery to see if there is a newer version of the module available. This check can be disabled with the new `DisableUpdateCheck` config property. The check happens asynchronously. The web request is kicked off when the module is loaded, but the result is only checked when the user runs a command. The result is logged at the Verbose level in any failure case as well as when the module is up-to-date. When there is a newer version available, it will write the message out as a Warning. The web request will only run once per day, and the user message will only be written once per day as well.
1 parent a1ba15e commit a9f48a8

File tree

5 files changed

+158
-2
lines changed

5 files changed

+158
-2
lines changed

Diff for: GitHubConfiguration.ps1

+8
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ function Set-GitHubConfiguration
110110
Specify this switch to stop the module from reporting any of its usage (which would be used
111111
for diagnostics purposes).
112112
113+
.PARAMETER DisableUpdateCheck
114+
Specify this switch to stop the daily update check with PowerShellGallery which can
115+
inform you when there is a newer version of this module available.
116+
113117
.PARAMETER LogPath
114118
The location of the log file that all activity will be written to if DisableLogging remains
115119
$false.
@@ -194,6 +198,8 @@ function Set-GitHubConfiguration
194198

195199
[switch] $DisableTelemetry,
196200

201+
[switch] $DisableUpdateCheck,
202+
197203
[string] $LogPath,
198204

199205
[switch] $LogProcessId,
@@ -281,6 +287,7 @@ function Get-GitHubConfiguration
281287
'DisablePiiProtection',
282288
'DisableSmarterObjects',
283289
'DisableTelemetry',
290+
'DisableUpdateCheck',
284291
'LogPath',
285292
'LogProcessId',
286293
'LogRequestBody',
@@ -615,6 +622,7 @@ function Import-GitHubConfiguration
615622
'disablePiiProtection' = $false
616623
'disableSmarterObjects' = $false
617624
'disableTelemetry' = $false
625+
'disableUpdateCheck' = $false
618626
'defaultNoStatus' = $false
619627
'defaultOwnerName' = [String]::Empty
620628
'defaultRepositoryName' = [String]::Empty

Diff for: GitHubCore.ps1

+2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ function Invoke-GHRestMethod
129129
[switch] $NoStatus
130130
)
131131

132+
Invoke-UpdateCheck
133+
132134
# Normalize our Uri fragment. It might be coming from a method implemented here, or it might
133135
# be coming from the Location header in a previous response. Either way, we don't want there
134136
# to be a leading "/" or trailing '/'

Diff for: PowerShellForGitHub.psd1

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
'GitHubTeams.ps1',
4444
'GitHubUsers.ps1',
4545
'NugetTools.ps1',
46-
'Telemetry.ps1')
46+
'Telemetry.ps1',
47+
'UpdateCheck.ps1')
4748

4849
# Minimum version of the Windows PowerShell engine required by this module
4950
PowerShellVersion = '4.0'

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ access token.
8585
If you ever wish to clear it in the future, just call `Clear-GitHubAuthentication`).
8686

8787
> For automated scenarios (like GithHub Actions) where you are dynamically getting the access token
88-
> needed for authentication, refer to `Example 2` in `Get-Help Set-StoreBrokerAuthentication -Examples`
88+
> needed for authentication, refer to `Example 2` in `Get-Help Set-GitHubAuthentication -Examples`
8989
> for how to configure in a promptless fashion.
9090
>
9191
> Alternatively, you _could_ configure PowerShell itself to always pass in a plain-text access token

Diff for: UpdateCheck.ps1

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
# We'll cache our job name and result so that we don't check more than once per day.
5+
$script:UpdateCheckJobName = $null
6+
$script:HasLatestVersion = $null
7+
8+
function Invoke-UpdateCheck
9+
{
10+
<#
11+
.SYNOPSIS
12+
Checks PowerShellGallery to see if a newer version of this module has been published.
13+
14+
.DESCRIPTION
15+
Checks PowerShellGallery to see if a newer version of this module has been published.
16+
17+
The check will only run once per day.
18+
19+
Runs asynchronously, so the user won't see any message until after they run their first
20+
API request after the module has been imported.
21+
22+
Will always assume true in the event of an incomplete or failed check.
23+
24+
Reports the result to the user via a Warning message (if a newer version is available)
25+
or a Verbose message (if they're running the latest version or the version check failed).
26+
27+
The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub
28+
29+
.EXAMPLE
30+
Invoke-UpdateCheck
31+
32+
.NOTES
33+
Internal-only helper method.
34+
#>
35+
param()
36+
37+
if (Get-GitHubConfiguration -Name DisableUpdateCheck)
38+
{
39+
return
40+
}
41+
42+
$moduleName = $MyInvocation.MyCommand.Module.Name
43+
$moduleVersion = $MyInvocation.MyCommand.Module.Version
44+
45+
$jobNameToday = "Invoke-UpdateCheck-" + (Get-Date -format 'yyyyMMdd')
46+
47+
# We only check once per day
48+
if ($jobNameToday -eq $script:UpdateCheckJobName)
49+
{
50+
# Have we retrieved the status yet? $null means we haven't.
51+
if ($null -ne $script:HasLatestVersion)
52+
{
53+
# We've already completed the check for today. No further action required.
54+
return
55+
}
56+
57+
$state = (Get-Job -Name $script:UpdateCheckJobName).state
58+
if ($state -eq 'Failed')
59+
{
60+
# We'll just assume we're up-to-date if we failed to check today.
61+
Write-Log -Message '[$moduleName] update check failed for today (web request failed). Assuming up-to-date.' -Level Verbose
62+
$script:HasLatestVersion = $true
63+
64+
# Clear out the job info (even though we don't need the result)
65+
$null = Receive-Job -Name $script:UpdateCheckJobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable errorInfo
66+
return
67+
}
68+
elseif ($state -eq 'Completed')
69+
{
70+
$result = Receive-Job -Name $script:UpdateCheckJobName -AutoRemoveJob -Wait
71+
try
72+
{
73+
# Occasionally the API puts two nearly identical XML responses in the body (each on a separate line).
74+
# We'll try to avoid an unnecessary failure by always using the first line of the response.
75+
$xml = [xml]($result.Content.Split([Environment]::NewLine) | Select-Object -First 1)
76+
$latestVersion = $xml.feed.entry.properties.version |
77+
ForEach-Object {[System.Version]$_} |
78+
Sort-Object -Descending |
79+
Select-Object -First 1
80+
81+
$script:HasLatestVersion = $latestVersion -eq $moduleVersion
82+
if ($script:HasLatestVersion)
83+
{
84+
Write-Log "[$moduleName] update check complete. Running latest version: $($latestVersion.ToString())" -Level Verbose
85+
}
86+
else
87+
{
88+
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
89+
}
90+
}
91+
catch
92+
{
93+
# This could happen if the server returned back an invalid (non-XML) response for the request
94+
# for some reason.
95+
Write-Log -Message "[$moduleName] update check failed for today (invalid XML response). Assuming up-to-date." -Level Verbose
96+
$script:HasLatestVersion = $true
97+
}
98+
99+
return
100+
}
101+
else
102+
{
103+
# It's still running. Nothing further for us to do right now. We'll check back
104+
# again next time.
105+
return
106+
}
107+
}
108+
else
109+
{
110+
# We either haven't checked yet, or it's a new day so we should check again.
111+
$script:UpdateCheckJobName = $jobNameToday
112+
$script:HasLatestVersion = $null
113+
}
114+
115+
[scriptblock]$scriptBlock = {
116+
param($ModuleName)
117+
118+
$params = @{}
119+
$params.Add('Uri', "https://www.powershellgallery.com/api/v2/FindPackagesById()?id='$ModuleName'")
120+
$params.Add('Method', 'Get')
121+
$params.Add('UseDefaultCredentials', $true)
122+
$params.Add('UseBasicParsing', $true)
123+
124+
try
125+
{
126+
Invoke-WebRequest @params
127+
}
128+
catch
129+
{
130+
# We will silently ignore any errors that occurred, but we need to make sure that
131+
# we are explicitly catching and throwing them so that our reported state is Failed.
132+
throw
133+
}
134+
}
135+
136+
$null = Start-Job -Name $script:UpdateCheckJobName -ScriptBlock $scriptBlock -Arg @($moduleName)
137+
138+
# We're letting this run asynchronously so that users can immediately start using the module.
139+
# We'll check back in on the result of this the next time they run any command.
140+
}
141+
142+
# We will explicitly run this as soon as the module has been loaded,
143+
# and then it will be called again during every major function call in the module
144+
# so that the result can be reported.
145+
Invoke-UpdateCheck

0 commit comments

Comments
 (0)