core/modules/monkeyutils/public/Update-MonkeyAsset.ps1
# Monkey365 - the PowerShell Cloud Security Tool for Azure and Microsoft 365 (copyright 2022) by Juan Garrido # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. Function Update-MonkeyAsset{ <# .SYNOPSIS Update assets from external source .DESCRIPTION Update assets from external source .INPUTS .OUTPUTS .EXAMPLE .NOTES Author : Juan Garrido Twitter : @tr1ana File Name : Update-MonkeyAsset Version : 1.0 .LINK https://github.com/silverhack/monkey365 #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope="Function")] [CmdletBinding(DefaultParameterSetName = 'Assets')] [OutputType([System.Boolean])] Param ( [Parameter(Mandatory=$true, HelpMessage="GitHub release url")] [String]$Url, [Parameter(Mandatory=$false, HelpMessage="Check integrity with SHA256")] [Switch]$SHA256, [Parameter(Mandatory=$false, HelpMessage="Check integrity with SHA256")] [Switch]$SHA512, [Parameter(Mandatory=$false, HelpMessage="Add a file with version ID")] [Switch]$IncludeVersionId, [parameter(Mandatory= $true, HelpMessage= "Directory output")] [System.IO.DirectoryInfo]$Output ) Begin{ #Add System.IO.Compression Add-Type -Assembly 'System.IO.Compression' $zip = $shaZip = $content = $method = $cryptography = $Hash = $version = $vId = $null; #Create dictionary with cryptograpy items $crypto = @{ sha256 = [System.Security.Cryptography.SHA256]::Create(); sha512 = [System.Security.Cryptography.SHA512]::Create(); } # Ensure that SHA256 is used if integrity mechanism is not provided $method = $PSBoundParameters.Keys.Where({$_ -like '*SHA*'}) If($method.Count -eq 0){ $method = "sha256" } Write-Information ("Using {0} method" -f $method.ToLower()) -InformationAction $InformationPreference $cryptography = $crypto.Item($method.ToLower()); #Check if cryptography is null If($null -eq $cryptography){ Write-Warning "Unable to determine integrity check method. Using default SHA256 method" $cryptography = $crypto.Item('sha256'); $method = "sha256" } If($Url.Contains('api.github.com')){ $repoUrl = $Url; } Else{ Try{ $repoUrl = ("https://api.github.com/repos/{0}/{1}/releases/latest" -f $Url.Split('/')[-2],$Url.Split('/')[-1]) } Catch{ Write-Error $_ return $null } } #Set headers $headers = @{ Accept = "application/json"; } #Get release Try{ If($null -ne $repoUrl){ $latest = Invoke-WebRequest -Uri $repoUrl -Headers $headers -UserAgent "Monkey365" -ErrorAction Ignore If($null -ne $latest -and $latest.StatusCode -eq [System.Net.HttpStatusCode]::OK){ Try{ $content = $latest.Content | ConvertFrom-Json -ErrorAction Ignore } Catch{ Write-Warning ("Unable to get JSON content from {0}" -f $Url) } } } } Catch{ Write-Warning $_.Exception.Message } } Process{ Try{ If($null -ne $content){ $vId = $content| Select-Object -ExpandProperty id -ErrorAction Ignore $vfile = ("{0}/version" -f $Output.FullName); If([System.IO.File]::Exists($vfile)){ Try{ $version = [System.IO.File]::ReadAllText($vfile); } Catch{ Write-Error $_.Exception return $false } } If($vId -eq $version){ $p = @{ Message = "All resources are up to date" Verbose = If($PSBoundParameters.ContainsKey('Verbose') -and $PSBoundParameters['Verbose'].IsPresent){$PSBoundParameters['Verbose'].IsPresent}Else{$false} } Write-Verbose @p return $true } Else{ $tagName = $content | Select-Object -ExpandProperty name -ErrorAction Ignore Write-Information ("Using latest release: {0}" -f $tagName) -InformationAction $InformationPreference #Get assets url $assetsUrl = $content.assets.Where({$_.name -like '*zip*' -and $_.content_type -eq 'application/zip'}) | Select-Object -ExpandProperty browser_download_url -ErrorAction Ignore If($assetsUrl){ Write-Information ("Downloading content from {0}" -f $assetsUrl) -InformationAction $InformationPreference $zip = Invoke-WebRequest -Uri $assetsUrl -UserAgent "Monkey365" -ErrorAction Ignore $array = $zip.RawContentStream.ToArray() [byte[]]$checksum = $cryptography.ComputeHash($array); $shaZip = [System.BitConverter]::ToString($checksum).Replace('-', [String]::Empty).ToLowerInvariant() } Else{ Write-Warning ("a File with extension zip was not found in {1}" -f $assetsUrl) return $false } #Get SHA from file $shaurl = $content.assets.Where({$_.name -like ('*{0}*' -f $method.ToLower())}) | Select-Object -ExpandProperty browser_download_url -ErrorAction Ignore If($shaurl){ Write-Information ("Downloading content from {0}" -f $shaurl) -InformationAction $InformationPreference $shaFile = Invoke-WebRequest -Uri $shaurl -UserAgent "Monkey365" -ErrorAction Ignore $sr = [System.IO.StreamReader]::new($shaFile.RawContentStream); $Hash = $sr.ReadToEnd(); $Hash = $Hash.Trim() $sr.Close(); $sr.Dispose(); } Else{ Write-Warning ("a File with extension {0} was not found in {1}" -f $method.ToLower(),$assetsUrl) return $false } } } If($null -ne $assetsUrl -and $null -ne $Hash -and $null -ne $shaZip){ Write-Information ("Verifying that {0} checksum for {1} is valid" -f $method.ToUpper(), $assetsUrl) -InformationAction $InformationPreference If(-NOT $Hash.Equals($shaZip)){ Write-Warning ("{0} checksum of {1} is not valid" -f $method.ToUpper(), $assetsUrl) return $false } Write-Information ("{0} checksum of {1} is valid" -f $method.ToUpper(), $assetsUrl) -InformationAction $InformationPreference $zipArchive = [System.IO.Compression.ZipArchive]::new($zip.RawContentStream,[System.IO.Compression.ZipArchiveMode]::Read) $allEntries = $zipArchive.Entries.Where({$_.FullName -notlike "*.git*" -and $_.FullName -ne "README.md"}); Foreach($entry in $allEntries){ Try{ If([String]::IsNullOrEmpty($entry.Name) -and $entry.FullName.EndsWith('/')){ $destination = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($Output.FullName,$entry.FullName)) $directory = [System.IO.Path]::GetDirectoryName($destination); If(![System.IO.Directory]::Exists($directory)){ Write-Information ("Creating directory {0}" -f $directory) -InformationAction $InformationPreference [void][System.IO.Directory]::CreateDirectory($directory); } } Else{ $destination = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($Output.FullName,$entry.FullName)) $fileStream = [System.IO.FileStream]::new($destination,[System.IO.FileMode]::OpenOrCreate) $stream = $entry.Open(); [void]$stream.CopyToAsync($fileStream).GetAwaiter().GetResult(); $fileStream.Close() $fileStream.Dispose() } } Catch{ Write-Warning $_.Exception.Message } } If($IncludeVersionId.IsPresent){ $versionId = $content| Select-Object -ExpandProperty id -ErrorAction Ignore If($versionId){ $outVersionFile = ("{0}{1}version" -f $Output.FullName,[System.IO.Path]::DirectorySeparatorChar) Write-Information ("Writing version file to {0}" -f $outVersionFile) -InformationAction $InformationPreference $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($outVersionFile, $versionId, $Utf8NoBomEncoding); } } return $true } } Catch{ Write-Error $_.Exception.Message return $false } } End{ #Nothing to do here } } |