2023-11-22 15:14:08 +01:00
function Install-Binary {
2020-04-21 11:58:27 +03:00
<#
. SYNOPSIS
2023-11-22 15:14:08 +01:00
A function to install binaries from either a URL or a local path .
2019-12-13 09:48:00 -05:00
2020-04-21 11:58:27 +03:00
. DESCRIPTION
2023-11-22 15:14:08 +01:00
This function downloads and installs . exe or . msi binaries from a specified URL or a local path . It also supports checking the binary's signature and SHA256/SHA512 sum before installation .
2019-12-13 09:48:00 -05:00
2020-04-21 11:58:27 +03:00
. PARAMETER Url
2023-11-22 15:14:08 +01:00
The URL from which the binary will be downloaded . This parameter is required if LocalPath is not specified .
2019-12-13 09:48:00 -05:00
2023-11-22 15:14:08 +01:00
. PARAMETER LocalPath
The local path of the binary to be installed . This parameter is required if Url is not specified .
2019-12-13 09:48:00 -05:00
2023-11-22 15:14:08 +01:00
. PARAMETER Type
The type of the binary to be installed . Valid values are "MSI" and "EXE" . If not specified, the type is inferred from the file extension .
. PARAMETER InstallArgs
The list of arguments that will be passed to the installer . Cannot be used together with ExtraInstallArgs .
. PARAMETER ExtraInstallArgs
Additional arguments that will be passed to the installer . Cannot be used together with InstallArgs .
. PARAMETER ExpectedSignature
The expected signature of the binary . If specified, the binary's signature is checked before installation .
. PARAMETER ExpectedSHA256Sum
The expected SHA256 sum of the binary . If specified, the binary's SHA256 sum is checked before installation .
. PARAMETER ExpectedSHA512Sum
The expected SHA512 sum of the binary . If specified, the binary's SHA512 sum is checked before installation .
2019-12-13 09:48:00 -05:00
2020-04-21 11:58:27 +03:00
. EXAMPLE
2023-11-22 15:14:08 +01:00
Install-Binary -Url "https://go . microsoft . com/fwlink/p/?linkid=2083338" -Type EXE -InstallArgs ("/features", "+", "/quiet") -ExpectedSignature "A5C7D5B7C838D5F89DDBEDB85B2C566B4CDA881F"
2020-04-21 11:58:27 +03:00
#>
2019-12-13 09:48:00 -05:00
2020-05-05 20:50:00 +03:00
Param
(
2023-11-22 15:14:08 +01:00
[ Parameter ( Mandatory , ParameterSetName = " Url " ) ]
2020-04-21 11:58:27 +03:00
[ String ] $Url ,
2023-11-22 15:14:08 +01:00
[ Parameter ( Mandatory , ParameterSetName = " LocalPath " ) ]
[ String ] $LocalPath ,
[ ValidateSet ( " MSI " , " EXE " ) ]
[ String ] $Type ,
[ String[] ] $InstallArgs ,
[ String[] ] $ExtraInstallArgs ,
[ String[] ] $ExpectedSignature ,
[ String ] $ExpectedSHA256Sum ,
[ String ] $ExpectedSHA512Sum
2019-12-13 09:48:00 -05:00
)
2023-11-22 15:14:08 +01:00
if ( $PSCmdlet . ParameterSetName -eq " LocalPath " ) {
if ( -not ( Test-Path -Path $LocalPath ) ) {
throw " LocalPath parameter is specified, but the file does not exist. "
}
if ( -not $Type ) {
$Type = ( [ System.IO.Path ] :: GetExtension ( $LocalPath ) ) . Replace ( " . " , " " ) . ToUpper ( )
if ( $Type -ne " MSI " -and $Type -ne " EXE " ) {
throw " LocalPath parameter is specified, but the file extension is not .msi or .exe. Please specify the Type parameter. "
}
}
$filePath = $LocalPath
} else {
if ( -not $Type ) {
$Type = ( [ System.IO.Path ] :: GetExtension ( $Url ) ) . Replace ( " . " , " " ) . ToUpper ( )
if ( $Type -ne " MSI " -and $Type -ne " EXE " ) {
throw " Cannot determine the file type from the URL. Please specify the Type parameter. "
}
2023-11-29 13:02:29 +01:00
$fileName = [ System.IO.Path ] :: GetFileName ( $Url )
} else {
$fileName = [ System.IO.Path ] :: GetFileNameWithoutExtension ( [ System.IO.Path ] :: GetRandomFileName ( ) ) + " . $Type " . ToLower ( )
2023-11-22 15:14:08 +01:00
}
2023-11-29 13:02:29 +01:00
$filePath = Invoke-DownloadWithRetry -Url $Url -Path " ${env:Temp} \ $fileName "
2021-09-09 10:46:21 +03:00
}
2019-12-13 09:48:00 -05:00
2023-11-22 15:14:08 +01:00
if ( $PSBoundParameters . ContainsKey ( 'ExpectedSignature' ) ) {
if ( $ExpectedSignature ) {
2023-11-30 09:22:14 +01:00
Test-FileSignature -Path $filePath -ExpectedThumbprint $ExpectedSignature
2023-11-22 15:14:08 +01:00
} else {
2023-09-22 10:52:16 +02:00
throw " ExpectedSignature parameter is specified, but no signature is provided. "
}
}
2023-11-22 15:14:08 +01:00
if ( $ExpectedSHA256Sum ) {
2023-11-30 09:22:14 +01:00
Test-FileChecksum $filePath -ExpectedSHA256Sum $ExpectedSHA256Sum
2023-11-22 15:14:08 +01:00
}
if ( $ExpectedSHA512Sum ) {
2023-11-30 09:22:14 +01:00
Test-FileChecksum $filePath -ExpectedSHA512Sum $ExpectedSHA512Sum
2023-11-22 15:14:08 +01:00
}
if ( $ExtraInstallArgs -and $InstallArgs ) {
throw " InstallArgs and ExtraInstallArgs parameters cannot be used together. "
}
2023-10-11 11:02:59 +02:00
2023-11-22 15:14:08 +01:00
if ( $Type -eq " MSI " ) {
# MSI binaries should be installed via msiexec.exe
if ( $ExtraInstallArgs ) {
$InstallArgs = @ ( '/i' , $filePath , '/qn' , '/norestart' ) + $ExtraInstallArgs
} elseif ( -not $InstallArgs ) {
Write-Host " No arguments provided for MSI binary. Using default arguments: /i, /qn, /norestart "
$InstallArgs = @ ( '/i' , $filePath , '/qn' , '/norestart' )
2022-01-20 15:59:37 +03:00
}
2020-04-21 11:58:27 +03:00
$filePath = " msiexec.exe "
2023-11-22 15:14:08 +01:00
} else {
# EXE binaries should be started directly
if ( $ExtraInstallArgs ) {
$InstallArgs = $ExtraInstallArgs
}
2020-04-21 11:58:27 +03:00
}
2019-12-13 09:48:00 -05:00
2023-11-22 15:14:08 +01:00
$installStartTime = Get-Date
Write-Host " Starting Install $Name ... "
try {
$process = Start-Process -FilePath $filePath -ArgumentList $InstallArgs -Wait -PassThru
2020-04-21 11:58:27 +03:00
$exitCode = $process . ExitCode
2021-09-20 16:20:56 +03:00
$installCompleteTime = [ math ] :: Round ( ( $ ( Get-Date ) - $installStartTime ) . TotalSeconds , 2 )
2023-11-22 15:14:08 +01:00
if ( $exitCode -eq 0 ) {
2021-09-20 16:20:56 +03:00
Write-Host " Installation successful in $installCompleteTime seconds "
2023-11-22 15:14:08 +01:00
} elseif ( $exitCode -eq 3010 ) {
Write-Host " Installation successful in $installCompleteTime seconds. Reboot is required. "
} else {
Write-Host " Installation process returned unexpected exit code: $exitCode "
Write-Host " Time elapsed: $installCompleteTime seconds "
2020-04-21 11:58:27 +03:00
exit $exitCode
2019-12-13 09:48:00 -05:00
}
2023-11-22 15:14:08 +01:00
} catch {
2021-09-20 16:20:56 +03:00
$installCompleteTime = [ math ] :: Round ( ( $ ( Get-Date ) - $installStartTime ) . TotalSeconds , 2 )
2023-11-22 15:14:08 +01:00
Write-Host " Installation failed in $installCompleteTime seconds "
2019-12-13 09:48:00 -05:00
}
}
2019-12-23 17:41:24 +04:00
2023-11-29 13:02:29 +01:00
function Invoke-DownloadWithRetry {
2023-12-04 10:50:53 +01:00
<#
. SYNOPSIS
Downloads a file from a given URL with retry functionality .
. DESCRIPTION
The Invoke-DownloadWithRetry function downloads a file from the specified URL
to the specified path . It includes retry functionality in case the download fails .
. PARAMETER Url
The URL of the file to download .
. PARAMETER Path
The path where the downloaded file will be saved . If not provided, a temporary path
will be used .
. EXAMPLE
Invoke-DownloadWithRetry -Url "https://example . com/file . zip" -Path "C:\Downloads\file . zip"
Downloads the file from the specified URL and saves it to the specified path .
. EXAMPLE
Invoke-DownloadWithRetry -Url "https://example . com/file . zip"
Downloads the file from the specified URL and saves it to a temporary path .
. OUTPUTS
The path where the downloaded file is saved .
#>
2020-06-30 07:48:55 +03:00
Param
2020-05-05 20:50:00 +03:00
(
2020-03-19 18:15:11 +07:00
[ Parameter ( Mandatory ) ]
[ string ] $Url ,
2023-11-29 13:02:29 +01:00
[ Alias ( " Destination " ) ]
[ string ] $Path
2020-04-02 23:29:22 +03:00
)
2023-11-29 13:02:29 +01:00
if ( -not $Path ) {
$invalidChars = [ IO.Path ] :: GetInvalidFileNameChars ( ) -join ''
$re = " [{0}] " -f [ RegEx ] :: Escape ( $invalidChars )
$fileName = [ IO.Path ] :: GetFileName ( $Url ) -replace $re
if ( [ String ] :: IsNullOrEmpty ( $fileName ) ) {
$fileName = [ System.IO.Path ] :: GetRandomFileName ( )
}
$Path = Join-Path -Path " ${env:Temp} " -ChildPath $fileName
2020-05-15 15:01:45 +03:00
}
2023-11-29 13:02:29 +01:00
Write-Host " Downloading package from $Url to $Path ... "
2022-04-20 16:23:32 +04:00
2023-11-29 13:02:29 +01:00
$interval = 30
$downloadStartTime = Get-Date
for ( $retries = 20 ; $retries -gt 0 ; $retries - - ) {
2023-11-22 15:14:08 +01:00
try {
2023-11-29 13:02:29 +01:00
$attemptStartTime = Get-Date
( New-Object System . Net . WebClient ) . DownloadFile ( $Url , $Path )
$attemptSeconds = [ math ] :: Round ( ( $ ( Get-Date ) - $attemptStartTime ) . TotalSeconds , 2 )
Write-Host " Package downloaded in $attemptSeconds seconds "
2020-04-02 23:29:22 +03:00
break
2023-11-22 15:14:08 +01:00
} catch {
2023-11-29 13:02:29 +01:00
$attemptSeconds = [ math ] :: Round ( ( $ ( Get-Date ) - $attemptStartTime ) . TotalSeconds , 2 )
Write-Warning " Package download failed in $attemptSeconds seconds "
Write-Warning $_ . Exception . Message
2023-12-04 10:50:53 +01:00
if ( $_ . Exception . InnerException . Response . StatusCode -eq [ System.Net.HttpStatusCode ] :: NotFound ) {
Write-Warning " Request returned 404 Not Found. Aborting download. "
$retries = 0
}
2020-03-19 02:38:23 +07:00
}
2023-11-29 13:02:29 +01:00
if ( $retries -eq 0 ) {
$totalSeconds = [ math ] :: Round ( ( $ ( Get-Date ) - $downloadStartTime ) . TotalSeconds , 2 )
throw " Package download failed after $totalSeconds seconds "
}
Write-Warning " Waiting $interval seconds before retrying (retries left: $retries )... "
Start-Sleep -Seconds $interval
2020-04-02 23:29:22 +03:00
}
2023-11-29 13:02:29 +01:00
return $Path
2020-03-19 02:38:23 +07:00
}
2023-11-29 12:58:37 +01:00
function Get-ToolsetContent {
2023-12-04 10:50:53 +01:00
<#
. SYNOPSIS
Retrieves the content of the toolset . json file .
. DESCRIPTION
2024-01-12 12:31:21 +01:00
This function reads the toolset . json file in path provided by IMAGE_FOLDER
environment variable and returns the content as a PowerShell object .
2023-12-04 10:50:53 +01:00
#>
2024-01-12 12:31:21 +01:00
$toolsetPath = Join-Path $env:IMAGE_FOLDER " toolset.json "
2021-08-03 11:56:52 +03:00
$toolsetJson = Get-Content -Path $toolsetPath -Raw
2020-04-30 18:11:40 +03:00
ConvertFrom-Json -InputObject $toolsetJson
}
2023-11-23 11:59:08 +01:00
function Get-TCToolPath {
<#
. SYNOPSIS
This function returns the full path of a tool in the tool cache .
. DESCRIPTION
The Get-TCToolPath function takes a tool name as a parameter and returns the full path of the tool in the tool cache .
It uses the AGENT_TOOLSDIRECTORY environment variable to determine the root path of the tool cache .
. PARAMETER ToolName
The name of the tool for which the path is to be returned .
. EXAMPLE
Get-TCToolPath -ToolName "Tool1"
This command returns the full path of "Tool1" in the tool cache .
#>
Param
(
[ string ] $ToolName
)
2020-07-16 07:30:34 +03:00
$toolcacheRootPath = Resolve-Path $env:AGENT_TOOLSDIRECTORY
return Join-Path $toolcacheRootPath $ToolName
}
2023-11-23 11:59:08 +01:00
function Get-TCToolVersionPath {
2020-06-30 07:48:55 +03:00
<#
2023-11-23 11:59:08 +01:00
. SYNOPSIS
This function returns the full path of a specific version of a tool in the tool cache .
2020-06-30 07:48:55 +03:00
. DESCRIPTION
2023-11-23 11:59:08 +01:00
The Get-TCToolVersionPath function takes a tool name, version, and architecture as parameters and returns the full path of the specified version of the tool in the tool cache .
It uses the Get-TCToolPath function to get the root path of the tool .
2020-06-30 07:48:55 +03:00
. PARAMETER Name
2023-11-23 11:59:08 +01:00
The name of the tool for which the path is to be returned .
2020-06-30 07:48:55 +03:00
. PARAMETER Version
2023-11-23 11:59:08 +01:00
The version of the tool for which the path is to be returned . If the version number is less than 3 parts, a wildcard is added .
2020-06-30 07:48:55 +03:00
. PARAMETER Arch
2023-11-23 11:59:08 +01:00
The architecture of the tool for which the path is to be returned . Defaults to "x64" .
. EXAMPLE
Get-TCToolVersionPath -Name "Tool1" -Version "1 . 0" -Arch "x86"
2020-06-30 07:48:55 +03:00
2023-11-23 11:59:08 +01:00
This command returns the full path of version "1 . 0" of "Tool1" for "x86" architecture in the tool cache .
#>
2020-06-30 07:48:55 +03:00
Param
(
2023-11-23 11:59:08 +01:00
[ Parameter ( Mandatory = $true ) ]
2020-06-30 07:48:55 +03:00
[ string ] $Name ,
2023-11-23 11:59:08 +01:00
[ Parameter ( Mandatory = $true ) ]
2020-06-30 07:48:55 +03:00
[ string ] $Version ,
[ string ] $Arch = " x64 "
)
2023-11-23 11:59:08 +01:00
$toolPath = Get-TCToolPath -ToolName $Name
2020-06-30 07:48:55 +03:00
# Add wildcard if missing
if ( $Version . Split ( " . " ) . Length -lt 3 ) {
$Version + = " .* "
}
2020-07-16 07:30:34 +03:00
$versionPath = Join-Path $toolPath $Version
2020-06-30 07:48:55 +03:00
# Take latest installed version in case if toolset version contains wildcards
2020-07-16 07:30:34 +03:00
$foundVersion = Get-Item $versionPath `
2023-11-30 09:22:14 +01:00
| Sort-Object -Property { [ version ] $_ . name } -Descending `
2023-11-23 11:59:08 +01:00
| Select-Object -First 1
2020-06-30 07:48:55 +03:00
2020-07-16 07:30:34 +03:00
if ( -not $foundVersion ) {
return $null
2020-06-30 07:48:55 +03:00
}
2020-07-16 07:30:34 +03:00
return Join-Path $foundVersion $Arch
2020-06-30 07:48:55 +03:00
}
2023-11-29 12:58:37 +01:00
function Test-IsWin22 {
2023-11-30 17:12:17 +01:00
<#
. SYNOPSIS
Checks if the current Windows operating system is Windows Server 2022 .
. DESCRIPTION
This function uses the Get-CimInstance cmdlet to retrieve information
about the current Windows operating system . It then checks if the Caption
property of the Win32_OperatingSystem class contains the string "2022",
indicating that the operating system is Windows Server 2022 .
. OUTPUTS
Returns $true if the current Windows operating system is Windows Server 2022 .
Otherwise, returns $false .
#>
( Get-CimInstance -ClassName Win32_OperatingSystem ) . Caption -match " 2022 "
2021-08-23 11:13:14 +03:00
}
2023-11-29 12:58:37 +01:00
function Test-IsWin19 {
2023-11-30 17:12:17 +01:00
<#
. SYNOPSIS
Checks if the current Windows operating system is Windows Server 2019 .
. DESCRIPTION
This function uses the Get-CimInstance cmdlet to retrieve information
about the current Windows operating system . It then checks if the Caption
property of the Win32_OperatingSystem class contains the string "2019",
indicating that the operating system is Windows Server 2019 .
. OUTPUTS
Returns $true if the current Windows operating system is Windows Server 2019 .
Otherwise, returns $false .
#>
( Get-CimInstance -ClassName Win32_OperatingSystem ) . Caption -match " 2019 "
2020-03-19 02:38:23 +07:00
}
2023-11-23 11:59:49 +01:00
function Expand-7ZipArchive {
2023-11-30 17:12:17 +01:00
<#
. SYNOPSIS
Extracts files from a 7-Zip archive .
. DESCRIPTION
This function uses the 7z . exe command-line tool to extract files from an archive .
The archive path, destination path, and extract method are specified as parameters .
. PARAMETER Path
The path to the archive .
. PARAMETER DestinationPath
The path to the directory where the files will be extracted .
. PARAMETER ExtractMethod
The method used to extract the files .
Valid values are "x" (extract with full paths) and "e" (extract without paths) .
. EXAMPLE
Expand-7ZipArchive -Path "C:\archive . 7z" -DestinationPath "C:\extracted" -ExtractMethod "x"
Extracts files from the "C:\archive . 7z" archive to the "C:\extracted" directory keeping the full paths .
#>
2020-06-30 07:48:55 +03:00
Param
2020-05-13 08:02:08 +03:00
(
2023-11-29 12:58:37 +01:00
[ Parameter ( Mandatory = $true ) ]
2023-11-30 09:22:14 +01:00
[ string ] $Path ,
2023-11-29 12:58:37 +01:00
[ Parameter ( Mandatory = $true ) ]
2023-11-30 09:22:14 +01:00
[ string ] $DestinationPath ,
2023-07-24 09:28:34 +02:00
[ ValidateSet ( " x " , " e " ) ]
2023-11-30 09:22:14 +01:00
[ char ] $ExtractMethod = " x "
2020-05-13 08:02:08 +03:00
)
Write-Host " Expand archive ' $PATH ' to ' $DestinationPath ' directory "
2023-07-24 09:28:34 +02:00
7z . exe $ExtractMethod " $Path " -o " $DestinationPath " -y | Out-Null
2020-05-13 08:02:08 +03:00
2023-11-29 12:58:37 +01:00
if ( $LASTEXITCODE -ne 0 ) {
2020-05-13 08:02:08 +03:00
Write-Host " There is an error during expanding ' $Path ' to ' $DestinationPath ' directory "
exit 1
}
}
2020-07-17 09:35:46 +00:00
2023-11-27 12:29:42 +01:00
function Get-WindowsUpdateStates {
<#
. SYNOPSIS
Retrieves the status of Windows updates .
. DESCRIPTION
The Get-WindowsUpdateStates function checks the Windows Event Log for specific event IDs related to Windows updates and returns a custom PowerShell object with the state and title of each update .
. PARAMETER None
This function does not take any parameters .
. OUTPUTS
PSCustomObject . This function returns a collection of custom PowerShell objects . Each object has two properties:
- State: A string that represents the state of the update . Possible values are "Installed", "Failed", and "Running" .
- Title: A string that represents the title of the update .
. NOTES
Event IDs used:
- 19: Installation Successful: Windows successfully installed the following update
- 20: Installation Failure: Windows failed to install the following update with error
- 43: Installation Started: Windows has started installing the following update
#>
$completedUpdates = @ { }
2021-11-16 17:14:17 +03:00
$filter = @ {
2023-11-27 12:29:42 +01:00
LogName = " System "
Id = 19 , 20 , 43
2021-11-16 17:14:17 +03:00
ProviderName = " Microsoft-Windows-WindowsUpdateClient "
}
$events = Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue | Sort-Object Id
foreach ( $event in $events ) {
switch ( $event . Id ) {
19 {
2023-11-27 12:29:42 +01:00
$state = " Installed "
2021-11-16 17:14:17 +03:00
$title = $event . Properties [ 0 ] . Value
2023-11-27 12:29:42 +01:00
$completedUpdates [ $title ] = " "
2021-11-16 17:14:17 +03:00
break
}
20 {
2023-11-27 12:29:42 +01:00
$state = " Failed "
2021-11-16 17:14:17 +03:00
$title = $event . Properties [ 1 ] . Value
2023-11-27 12:29:42 +01:00
$completedUpdates [ $title ] = " "
2021-11-16 17:14:17 +03:00
break
}
43 {
2023-11-27 12:29:42 +01:00
$state = " Running "
2021-11-16 17:14:17 +03:00
$title = $event . Properties [ 0 ] . Value
2021-12-02 10:56:43 +03:00
break
2021-11-16 17:14:17 +03:00
}
}
2023-11-27 12:29:42 +01:00
# Skip update started event if it was already completed
if ( $state -eq " Running " -and $completedUpdates . ContainsKey ( $title ) ) {
2021-11-16 17:14:17 +03:00
continue
}
[ PSCustomObject ] @ {
2023-11-27 12:29:42 +01:00
State = $state
2021-11-16 17:14:17 +03:00
Title = $title
}
}
}
2021-11-26 17:49:05 +03:00
2023-11-29 12:58:37 +01:00
function Invoke-ScriptBlockWithRetry {
<#
. SYNOPSIS
Executes a script block with retry logic .
. DESCRIPTION
The Invoke-ScriptBlockWithRetry function executes a specified script block with retry logic . It allows you to specify the number of retries and the interval between retries .
. PARAMETER Command
The script block to be executed .
. PARAMETER RetryCount
The number of times to retry executing the script block . The default value is 10 .
. PARAMETER RetryIntervalSeconds
The interval in seconds between each retry . The default value is 5 .
. EXAMPLE
Invoke-ScriptBlockWithRetry -Command { Get-Process } -RetryCount 3 -RetryIntervalSeconds 10
This example executes the script block { Get-Process } with 3 retries and a 10-second interval between each retry .
#>
2021-11-26 17:49:05 +03:00
param (
[ scriptblock ] $Command ,
[ int ] $RetryCount = 10 ,
2021-12-02 10:56:43 +03:00
[ int ] $RetryIntervalSeconds = 5
2021-11-26 17:49:05 +03:00
)
while ( $RetryCount -gt 0 ) {
try {
& $Command
return
2023-11-29 12:58:37 +01:00
} catch {
2021-12-02 10:56:43 +03:00
Write-Host " There is an error encountered: `n $_ "
2021-11-26 17:49:05 +03:00
$RetryCount - -
if ( $RetryCount -eq 0 ) {
exit 1
}
2021-12-02 10:56:43 +03:00
Write-Host " Waiting $RetryIntervalSeconds seconds before retrying. Retries left: $RetryCount "
Start-Sleep -Seconds $RetryIntervalSeconds
2021-11-26 17:49:05 +03:00
}
}
}
2021-12-08 10:39:42 +03:00
2023-11-30 17:12:17 +01:00
function Get-GithubReleasesByVersion {
<#
. SYNOPSIS
Retrieves GitHub releases for a specified repository based on version .
. DESCRIPTION
The function retrieves GitHub releases for a specified repository based on the
version provided . It supports filtering by version, allowing for the retrieval
of specific releases or the latest release . The function utilizes the GitHub REST API
to fetch the releases and caches the results to improve performance and reduce
the number of API calls .
. PARAMETER Repository
The name of the GitHub repository in the format "owner/repo" .
. PARAMETER Version
The version of the release to retrieve . It can be a specific version number,
"latest" to retrieve the latest release, or a wildcard pattern to match multiple versions .
. PARAMETER AllowPrerelease
Specifies whether to include prerelease versions in the results . By default,
prerelease versions are excluded .
2023-12-04 10:50:53 +01:00
. PARAMETER WithAssetsOnly
Specifies whether to exclude releases without assets . By default, releases without
assets are included .
2023-11-30 17:12:17 +01:00
. EXAMPLE
Get-GithubReleasesByVersion -Repository "Microsoft/PowerShell" -Version "7 . 2 . 0"
Retrieves the GitHub releases for the "Microsoft/PowerShell" repository with the version "7 . 2 . 0" .
. EXAMPLE
Get-GithubReleasesByVersion -Repository "Microsoft/PowerShell" -Version "latest"
Retrieves the latest GitHub release for the "Microsoft/PowerShell" repository .
. EXAMPLE
Get-GithubReleasesByVersion -Repository "Microsoft/PowerShell" -Version "7 . *"
Retrieves all GitHub releases for the "Microsoft/PowerShell" repository with versions starting with "7 . " .
#>
2021-12-08 10:39:42 +03:00
param (
2023-11-28 15:08:03 +01:00
[ Parameter ( Mandatory = $true ) ]
[ Alias ( " Repo " ) ]
[ string ] $Repository ,
2023-11-30 17:12:17 +01:00
[ string ] $Version = " * " ,
2023-12-04 10:50:53 +01:00
[ switch ] $AllowPrerelease ,
[ switch ] $WithAssetsOnly
2021-12-08 10:39:42 +03:00
)
2023-11-30 17:12:17 +01:00
$localCacheFile = Join-Path $ { env : TEMP } " github-releases_ $( $Repository -replace " / " , " _ " ) .json "
2023-11-28 15:08:03 +01:00
2023-11-30 17:12:17 +01:00
if ( Test-Path $localCacheFile ) {
$releases = Get-Content $localCacheFile | ConvertFrom-Json
Write-Debug " Found cached releases for ${Repository} in local file "
Write-Debug " Release count: $( $releases . Count ) "
} else {
$releases = @ ( )
$page = 1
$pageSize = 100
do {
$releasesPage = Invoke-RestMethod -Uri " https://api.github.com/repos/ ${Repository} /releases?per_page= ${pageSize} &page= ${page} "
$releases + = $releasesPage
$page + +
} while ( $releasesPage . Count -eq $pageSize )
Write-Debug " Found $( $releases . Count ) releases for ${Repository} "
Write-Debug " Caching releases for ${Repository} in local file "
$releases | ConvertTo-Json -Depth 10 | Set-Content $localCacheFile
}
2023-11-28 15:08:03 +01:00
if ( -not $releases ) {
throw " Failed to get releases from ${Repository} "
2021-12-08 10:39:42 +03:00
}
2022-06-14 09:34:46 +02:00
2023-12-04 10:50:53 +01:00
if ( $WithAssetsOnly ) {
$releases = $releases . Where { $_ . assets }
}
2023-11-28 15:08:03 +01:00
if ( -not $AllowPrerelease ) {
$releases = $releases . Where { $_ . prerelease -eq $false }
}
Write-Debug " Found $( $releases . Count ) releases with assets for ${Repository} "
2023-10-09 10:55:12 +02:00
2023-11-28 15:08:03 +01:00
# Parse version from tag name and put it to parameter Version
foreach ( $release in $releases ) {
$release | Add-Member -MemberType NoteProperty -Name version -Value (
$release . tag_name | Select-String -Pattern " \d+.\d+.\d+ " | ForEach-Object { $_ . Matches . Value }
)
2023-10-09 10:55:12 +02:00
}
2023-11-28 15:08:03 +01:00
# Sort releases by version
2023-11-30 09:22:14 +01:00
$releases = $releases | Sort-Object -Descending { [ version ] $_ . version }
2023-11-28 15:08:03 +01:00
# Select releases matching version
if ( $Version -eq " latest " ) {
$matchingReleases = $releases | Select-Object -First 1
} elseif ( $Version . Contains ( " * " ) ) {
$matchingReleases = $releases | Where-Object { $_ . version -like " $Version " }
2023-10-09 10:55:12 +02:00
} else {
2023-11-28 15:08:03 +01:00
$matchingReleases = $releases | Where-Object { $_ . version -eq " $Version " }
}
2023-10-09 10:55:12 +02:00
2023-11-28 15:08:03 +01:00
if ( -not $matchingReleases ) {
throw " Failed to get releases from ${Repository} matching version `" ${Version} `" . `n Available versions: $( $availableVersions -join " , " ) "
}
Write-Debug " Found $( $matchingReleases . Count ) releases matching version ${Version} for ${Repository} "
2023-11-30 17:12:17 +01:00
return $matchingReleases
}
function Resolve-GithubReleaseAssetUrl {
<#
. SYNOPSIS
Resolves the download URL for a specific asset in a GitHub release .
. DESCRIPTION
This function retrieves the download URL for a specific asset in a GitHub release .
It takes the repository name, version, and a URL match pattern as input parameters .
It searches for releases that match the specified version and then looks
for a download URL that matches the provided pattern . If a matching URL is found,
it returns the URL . If no matching URL is found, an exception is thrown .
. PARAMETER Repository
The name of the GitHub repository in the format "owner/repo" .
. PARAMETER Version
The version of the release to retrieve . It can be a specific version number,
"latest" to retrieve the latest release, or a wildcard pattern to match multiple versions .
. PARAMETER AllowPrerelease
Specifies whether to include prerelease versions in the results . By default,
prerelease versions are excluded .
. PARAMETER UrlMatchPattern
The pattern to match against the download URLs of the release assets .
Wildcards (*) can be used to match any characters .
2023-12-11 11:59:52 +01:00
. PARAMETER AllowMultipleMatches
Specifies whether to choose one of multiple assets matching the pattern or consider this behavior to be erroneous .
By default, multiple matches are not considered normal behavior and result in an error .
2023-11-30 17:12:17 +01:00
. EXAMPLE
Resolve-GithubReleaseAssetUrl -Repository "myrepo" -Version "1 . 0" -UrlMatchPattern "* . zip"
Retrieves the download URL for the asset in the "myrepo" repository with version "1 . 0" and a file extension of " . zip" .
#>
param (
[ Parameter ( Mandatory = $true ) ]
[ Alias ( " Repo " ) ]
[ string ] $Repository ,
[ string ] $Version = " * " ,
[ switch ] $AllowPrerelease ,
[ Parameter ( Mandatory = $true ) ]
[ Alias ( " Pattern " , " File " , " Asset " ) ]
2023-12-11 11:59:52 +01:00
[ string ] $UrlMatchPattern ,
[ switch ] $AllowMultipleMatches = $false
2023-11-30 17:12:17 +01:00
)
$matchingReleases = Get-GithubReleasesByVersion `
-Repository $Repository `
-AllowPrerelease: $AllowPrerelease `
2023-12-04 10:50:53 +01:00
-Version $Version `
-WithAssetsOnly
2023-11-30 17:12:17 +01:00
# Add wildcard to the beginning of the pattern if it's not there
if ( $UrlMatchPattern . Substring ( 0 , 2 ) -ne " */ " ) {
$UrlMatchPattern = " */ $UrlMatchPattern "
}
2023-11-28 15:08:03 +01:00
# Loop over releases until we find a download url matching the pattern
foreach ( $release in $matchingReleases ) {
$matchedVersion = $release . version
2023-12-04 10:50:53 +01:00
$matchedUrl = ( [ string[] ] $release . assets . browser_download_url ) -like $UrlMatchPattern
2023-11-28 15:08:03 +01:00
if ( $matchedUrl ) {
break
2023-10-09 10:55:12 +02:00
}
2022-06-14 09:34:46 +02:00
}
2023-11-28 15:08:03 +01:00
if ( -not $matchedUrl ) {
Write-Debug " Found no download urls matching pattern ${UrlMatchPattern} "
Write-Debug " Available download urls: `n $( $matchingReleases . assets . browser_download_url -join " `n " ) "
throw " No assets found in ${Repository} matching version `" ${Version} `" and pattern `" ${UrlMatchPattern} `" "
2023-12-11 11:59:52 +01:00
}
# If multiple urls match the pattern, sort them and take the last one
# Will only work with simple number series of no more than nine in a row.
if ( $matchedUrl . Count -gt 1 ) {
if ( $AllowMultipleMatches ) {
Write-Debug " Found multiple download urls matching pattern ${UrlMatchPattern} : `n $( $matchedUrl -join " `n " ) "
Write-Host " Performing sorting of urls to find the most recent version matching the pattern "
$matchedUrl = $matchedUrl | Sort-Object -Descending
$matchedUrl = $matchedUrl [ 0 ]
} else {
throw " Found multiple assets in ${Repository} matching version `" ${Version} `" and pattern `" ${UrlMatchPattern} `" . `n Available assets: `n $( $matchedUrl -join " `n " ) "
}
2023-10-09 10:55:12 +02:00
}
2021-12-08 10:39:42 +03:00
2023-11-28 15:08:03 +01:00
Write-Host " Found download url for ${Repository} version ${matchedVersion} : ${matchedUrl} "
return ( $matchedUrl -as [ string ] )
2021-12-08 10:39:42 +03:00
}
2023-01-12 23:25:12 +03:00
2023-12-04 10:50:53 +01:00
function Get-ChecksumFromGithubRelease {
2023-11-30 17:12:17 +01:00
<#
. SYNOPSIS
Retrieves the hash value of a specific file from a GitHub release body .
. DESCRIPTION
2023-12-04 10:50:53 +01:00
The Get-ChecksumFromGithubRelease function retrieves the hash value (SHA256 or SHA512)
2023-11-30 17:12:17 +01:00
of a specific file from a GitHub release . It searches for the file in the release body
and returns the hash value if found .
. PARAMETER Repository
The name of the GitHub repository in the format "owner/repo" .
. PARAMETER Version
The version of the release to inspect . It can be a specific version number,
"latest" to retrieve the latest release, or a wildcard pattern to match multiple versions .
. PARAMETER AllowPrerelease
Specifies whether to include prerelease versions in the results . By default,
prerelease versions are excluded .
. PARAMETER FileName
The name of the file to retrieve the hash value for .
. PARAMETER HashType
The type of hash value to retrieve . Valid values are "SHA256" and "SHA512" .
. EXAMPLE
2023-12-04 10:50:53 +01:00
Get-ChecksumFromGithubRelease -Repository "MyRepo" -FileName "myfile . txt" -HashType "SHA256"
2023-11-30 17:12:17 +01:00
Retrieves the SHA256 hash value of "myfile . txt" from the latest release of the "MyRepo" repository .
. EXAMPLE
2023-12-04 10:50:53 +01:00
Get-ChecksumFromGithubRelease -Repository "MyRepo" -Version "1 . 0" -FileName "myfile . txt" -HashType "SHA512"
2023-11-30 17:12:17 +01:00
Retrieves the SHA512 hash value of "myfile . txt" from the release version "1 . 0" of the "MyRepo" repository .
#>
2023-09-18 12:00:27 +02:00
param (
2023-11-29 12:58:37 +01:00
[ Parameter ( Mandatory = $true ) ]
2023-11-30 17:12:17 +01:00
[ Alias ( " Repo " ) ]
[ string ] $Repository ,
[ string ] $Version = " * " ,
[ switch ] $AllowPrerelease ,
[ Parameter ( Mandatory = $true ) ]
[ Alias ( " File " , " Asset " ) ]
2023-11-30 09:22:14 +01:00
[ string ] $FileName ,
2023-11-30 17:12:17 +01:00
[ Parameter ( Mandatory = $true ) ]
[ ValidateSet ( " SHA256 " , " SHA512 " ) ]
[ string ] $HashType
2023-09-18 12:00:27 +02:00
)
2023-11-30 17:12:17 +01:00
$matchingReleases = Get-GithubReleasesByVersion `
-Repository $Repository `
-AllowPrerelease: $AllowPrerelease `
2023-12-04 10:50:53 +01:00
-Version $Version `
-WithAssetsOnly
2023-11-30 17:12:17 +01:00
foreach ( $release in $matchingReleases ) {
$matchedVersion = $release . version
$matchedBody = $release . body
$matchedLine = $matchedBody . Split ( " `n " ) | Where-Object { $_ -like " * $FileName * " }
if ( $matchedLine . Count -gt 1 ) {
throw " Found multiple lines matching file name ' ${FileName} ' in body of release ${matchedVersion} . "
} elseif ( $matchedLine . Count -ne 0 ) {
break
2023-09-18 12:00:27 +02:00
}
}
2023-11-30 17:12:17 +01:00
if ( -not $matchedLine ) {
throw " File name ' ${FileName} ' not found in release body. "
2023-09-18 12:00:27 +02:00
}
2023-11-30 17:12:17 +01:00
Write-Debug " Found line matching file name ' ${FileName} ' in body of release ${matchedVersion} : `n ${matchedLine} "
if ( $HashType -eq " SHA256 " ) {
$pattern = " [A-Fa-f0-9]{64} "
} elseif ( $HashType -eq " SHA512 " ) {
$pattern = " [A-Fa-f0-9]{128} "
} else {
throw " Unknown hash type: ${HashType} "
2023-09-18 12:00:27 +02:00
}
2023-11-30 17:12:17 +01:00
$hash = $matchedLine | Select-String -Pattern $pattern | ForEach-Object { $_ . Matches . Value }
if ( [ string ] :: IsNullOrEmpty ( $hash ) ) {
throw " Found ' ${FileName} ' in body of release ${matchedVersion} , but failed to get hash from it. `n Line: ${matchedLine} "
}
Write-Host " Found hash for ${FileName} in release ${matchedVersion} : $hash "
return $hash
2023-09-18 12:00:27 +02:00
}
2023-11-30 09:22:14 +01:00
2023-12-04 10:50:53 +01:00
function Get-ChecksumFromUrl {
<#
. SYNOPSIS
Retrieves the checksum hash for a file from a given URL .
. DESCRIPTION
The Get-ChecksumFromUrl function retrieves the checksum hash for a specified file
from a given URL . It supports SHA256 and SHA512 hash types .
. PARAMETER Url
The URL of the checksum file .
. PARAMETER FileName
The name of the file to retrieve the checksum hash for .
. PARAMETER HashType
The type of hash to retrieve . Valid values are "SHA256" and "SHA512" .
. EXAMPLE
Get-ChecksumFromUrl -Url "https://example . com/checksums . txt" -FileName "file . txt" -HashType "SHA256"
Retrieves the SHA256 checksum hash for the file "file . txt" from the URL "https://example . com/checksums . txt" .
#>
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Url ,
[ Parameter ( Mandatory = $true ) ]
[ Alias ( " File " , " Asset " ) ]
[ string ] $FileName ,
[ Parameter ( Mandatory = $true ) ]
[ ValidateSet ( " SHA256 " , " SHA512 " ) ]
[ Alias ( " Type " ) ]
[ string ] $HashType
)
$tempFile = Join-Path -Path $env:TEMP -ChildPath ( [ System.IO.Path ] :: GetRandomFileName ( ) )
$checksums = ( Invoke-DownloadWithRetry -Url $Url -Path $tempFile | Get-Item | Get-Content ) -as [ string[] ]
Remove-Item -Path $tempFile
$matchedLine = $checksums | Where-Object { $_ -like " * $FileName * " }
if ( $matchedLine . Count -gt 1 ) {
throw " Found multiple lines matching file name ' ${FileName} ' in checksum file. "
} elseif ( $matchedLine . Count -eq 0 ) {
throw " File name ' ${FileName} ' not found in checksum file. "
}
if ( $HashType -eq " SHA256 " ) {
$pattern = " [A-Fa-f0-9]{64} "
} elseif ( $HashType -eq " SHA512 " ) {
$pattern = " [A-Fa-f0-9]{128} "
} else {
throw " Unknown hash type: ${HashType} "
}
Write-Debug " Found line matching file name ' ${FileName} ' in checksum file: `n ${matchedLine} "
$hash = $matchedLine | Select-String -Pattern $pattern | ForEach-Object { $_ . Matches . Value }
if ( [ string ] :: IsNullOrEmpty ( $hash ) ) {
throw " Found ' ${FileName} ' in checksum file, but failed to get hash from it. `n Line: ${matchedLine} "
}
Write-Host " Found hash for ${FileName} in checksum file: $hash "
return $hash
}
2023-11-30 09:22:14 +01:00
function Test-FileChecksum {
<#
. SYNOPSIS
Verifies the checksum of a file .
. DESCRIPTION
The Test-FileChecksum function verifies the SHA256 or SHA512 checksum of a file against an expected value .
If the checksum does not match the expected value, the function throws an error .
. PARAMETER Path
The path to the file for which to verify the checksum .
. PARAMETER ExpectedSHA256Sum
The expected SHA256 checksum . If this parameter is provided, the function will calculate the SHA256 checksum of the file and compare it to this value .
. PARAMETER ExpectedSHA512Sum
The expected SHA512 checksum . If this parameter is provided, the function will calculate the SHA512 checksum of the file and compare it to this value .
. EXAMPLE
Test-FileChecksum -Path "C:\temp\file . txt" -ExpectedSHA256Sum "ABC123"
Verifies that the SHA256 checksum of the file at C:\temp\file . txt is ABC123 .
. EXAMPLE
Test-FileChecksum -Path "C:\temp\file . txt" -ExpectedSHA512Sum "DEF456"
Verifies that the SHA512 checksum of the file at C:\temp\file . txt is DEF456 .
#>
param (
[ Parameter ( Mandatory = $true , Position = 0 ) ]
[ string ] $Path ,
[ Parameter ( Mandatory = $false ) ]
[ String ] $ExpectedSHA256Sum ,
[ Parameter ( Mandatory = $false ) ]
[ String ] $ExpectedSHA512Sum
)
Write-Verbose " Performing checksum verification "
if ( $ExpectedSHA256Sum -and $ExpectedSHA512Sum ) {
throw " Only one of the ExpectedSHA256Sum and ExpectedSHA512Sum parameters can be provided "
}
if ( -not ( Test-Path $Path ) ) {
throw " File not found: $Path "
}
if ( $ExpectedSHA256Sum ) {
$fileHash = ( Get-FileHash -Path $Path -Algorithm SHA256 ) . Hash
$expectedHash = $ExpectedSHA256Sum
}
if ( $ExpectedSHA512Sum ) {
$fileHash = ( Get-FileHash -Path $Path -Algorithm SHA512 ) . Hash
$expectedHash = $ExpectedSHA512Sum
}
if ( $fileHash -ne $expectedHash ) {
throw " Checksum verification failed: expected $expectedHash , got $fileHash "
} else {
Write-Verbose " Checksum verification passed "
}
}
2023-09-22 10:52:16 +02:00
function Test-FileSignature {
2023-11-30 09:22:14 +01:00
<#
. SYNOPSIS
Tests the file signature of a given file .
. DESCRIPTION
The Test-FileSignature function checks the signature of a file against the expected thumbprints .
It uses the Get-AuthenticodeSignature cmdlet to retrieve the signature information of the file .
If the signature status is not valid or the thumbprint does not match the expected thumbprints, an exception is thrown .
. PARAMETER Path
Specifies the path of the file to test .
. PARAMETER ExpectedThumbprint
Specifies the expected thumbprints to match against the file's signature .
. EXAMPLE
Test-FileSignature -Path "C:\Path\To\File . exe" -ExpectedThumbprint "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0"
This example tests the signature of the file "C:\Path\To\File . exe" against the expected thumbprint "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0" .
#>
2023-09-22 10:52:16 +02:00
param (
2023-11-30 09:22:14 +01:00
[ Parameter ( Mandatory = $true , Position = 0 ) ]
[ string ] $Path ,
2023-11-29 12:58:37 +01:00
[ Parameter ( Mandatory = $true ) ]
2023-11-30 09:22:14 +01:00
[ string[] ] $ExpectedThumbprint
2023-09-22 10:52:16 +02:00
)
2023-10-11 11:02:59 +02:00
2023-11-30 09:22:14 +01:00
$signature = Get-AuthenticodeSignature $Path
2023-10-11 11:02:59 +02:00
2023-09-22 10:52:16 +02:00
if ( $signature . Status -ne " Valid " ) {
throw " Signature status is not valid. Status: $( $signature . Status ) "
}
2023-10-11 11:02:59 +02:00
foreach ( $thumbprint in $ExpectedThumbprint ) {
if ( $signature . SignerCertificate . Thumbprint . Contains ( $thumbprint ) ) {
2023-11-30 09:22:14 +01:00
Write-Output " Signature for $Path is valid "
2023-10-11 11:02:59 +02:00
$signatureMatched = $true
return
}
2023-09-22 10:52:16 +02:00
}
2023-10-11 11:02:59 +02:00
if ( $signatureMatched ) {
2023-11-30 09:22:14 +01:00
Write-Output " Signature for $Path is valid "
2023-11-29 12:58:37 +01:00
} else {
2023-10-11 11:02:59 +02:00
throw " Signature thumbprint do not match expected. "
}
2023-10-09 10:55:12 +02:00
}
2023-11-29 13:00:16 +01:00
function Update-Environment {
<#
. SYNOPSIS
Updates the environment variables by reading values from the registry .
. DESCRIPTION
This function updates current environment by reading values from the registry .
It is useful when you need to update the environment variables without restarting the current session .
. NOTES
The function requires administrative privileges to modify the system registry .
#>
$locations = @ (
'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' ,
'HKCU:\Environment'
)
# Update PATH variable
$pathItems = $locations | ForEach-Object {
( Get-Item $_ ) . GetValue ( 'PATH' ) . Split ( ';' )
} | Select-Object -Unique
2023-12-11 22:23:36 +01:00
$env:PATH = $pathItems -join ';'
2023-11-29 13:00:16 +01:00
# Update other variables
$locations | ForEach-Object {
$key = Get-Item $_
foreach ( $name in $key . GetValueNames ( ) ) {
$value = $key . GetValue ( $name )
if ( -not ( $name -ieq 'PATH' ) ) {
Set-Item -Path Env : $name -Value $value
}
}
}
}