@@ -197,6 +197,20 @@ function Find-First {
197197 return @ (Get-ChildItem - Filter " ${Filter} " - Path " ${Path} " )[0 ]
198198}
199199
200+ function Get-CertificateInfo {
201+ <#
202+ . SYNOPSIS
203+ Get basic info of a certificate as a string
204+ #>
205+ [OutputType ([string ])]
206+ param ([Parameter (Mandatory = $true )][System.Security.Cryptography.X509Certificates.X509Certificate ]$Certificate )
207+ $NotBefore = $Certificate.NotBefore.ToUniversalTime ().ToString(" yyyy-MM-dd" )
208+ $NotAfter = $Certificate.NotAfter.ToUniversalTime ().ToString(" yyyy-MM-dd" )
209+ $Algorithm = $Certificate.SignatureAlgorithm.FriendlyName
210+ return " $ ( $Certificate.Subject ) (${NotBefore} - ${NotAfter} , ${Algorithm} , " `
211+ + " Currently valid: $ ( $Certificate.Verify ()) , Thumbprint: $ ( $Certificate.Thumbprint ) )"
212+ }
213+
200214function Get-InstallBitness {
201215 [OutputType ([string ])]
202216 param (
@@ -309,8 +323,10 @@ function Install-Executable {
309323 [OutputType ([int ])]
310324 param (
311325 [string ]$Name ,
312- [string ]$Path
326+ [string ]$Path ,
327+ [switch ]$BypassAuthenticode = $false
313328 )
329+ # Validate the file path
314330 try {
315331 $File = Get-Item " ${Path} " - ErrorAction Stop
316332 } catch {
@@ -323,14 +339,23 @@ function Install-Executable {
323339 }
324340 return 1
325341 }
342+ # Validate Authenticode
343+ if (-not $BypassAuthenticode ) {
344+ try {
345+ Test-AuthenticodeSignature - FilePath " ${Path} "
346+ } catch {
347+ return 2
348+ }
349+ }
350+ # Install the executable
326351 Show-Output " Installing ${Name} "
327352 if ($File.Extension -eq " .msi" ) {
328353 $Process = Start-Process - NoNewWindow - Wait - PassThru " msiexec" - ArgumentList " /i" , " ${Path} "
329354 } else {
330355 $Process = Start-Process - NoNewWindow - Wait - PassThru " ${Path} "
331356 }
332357 $ExitCode = $Process.ExitCode
333- if ($ExitCode -ne 0 ) {
358+ if ($ExitCode ) {
334359 Show-Output - ForegroundColor Red " ${Name} installation returned non-zero exit code ${ExitCode} . Perhaps the installation failed?"
335360 }
336361 return $ExitCode
@@ -346,34 +371,71 @@ function Install-FromUri {
346371 [Parameter (mandatory = $false )][string ]$UnzipFolderName ,
347372 [Parameter (mandatory = $false )][string ]$UnzippedFilePath ,
348373 [Parameter (mandatory = $false )][string ]$MD5 ,
349- [Parameter (mandatory = $false )][string ]$SHA256
374+ [Parameter (mandatory = $false )][string ]$SHA256 ,
375+ [switch ]$BypassAuthenticode = $false
350376 )
351377 Show-Output " Downloading ${Name} "
352378 $Path = " ${Downloads} \${Filename} "
353- Invoke-WebRequestFast - Uri " ${Uri} " - OutFile " ${Path} "
379+
380+ # Test if the file already exists and matches the given hash
381+ $FileAlreadyOK = $false
382+ if (Test-Path $Path ) {
383+ if ($PSBoundParameters.ContainsKey (" SHA256" )) {
384+ $FileHash = (Get-FileHash - Path " ${Path} " - Algorithm " SHA256" ).Hash
385+ if ($FileHash -eq $SHA256 ) {
386+ $FileAlreadyOK = $true
387+ Show-Output " The file `" ${Path} `" is already downloaded and has the correct SHA-256 hash ${SHA256} . Skipping download."
388+ } else {
389+ Show-Output " The file `" ${Path} ` was already downloaded, but had an incorrect SHA-256 hash. " `
390+ + " (Expected ${SHA256} , got ${FileHash} .) The previous download may have been interrupted. Downloading again."
391+ }
392+ } elseif ($PSBoundParameters.ContainsKey (" MD5" )) {
393+ $FileHash = (Get-FileHash - Path " ${Path} " - Algorithm " MD5" ).Hash
394+ if ($FileHash -eq $MD5 ) {
395+ $FileAlreadyOK = $true
396+ Show-Output " The file `" ${Path} `" is already downloaded and has the correct MD5 hash ${MD5} . Skipping download."
397+ } else {
398+ Show-Output " The file `" ${Path} ` was already downloaded, but had an incorrect MD5 hash. " `
399+ + " (Expected ${MD5} , got ${FileHash} .) The previous download may have been interrupted. Downloading again."
400+ }
401+ }
402+ }
403+
404+ # Download the file
405+ if (-not $FileAlreadyOK ) {
406+ Invoke-WebRequestFast - Uri " ${Uri} " - OutFile " ${Path} "
407+ }
408+
409+ # Get the file object
354410 try {
355411 $File = Get-Item " ${Path} " - ErrorAction Stop
356412 } catch {
357413 Show-Output " Downloaded file was not found at ${Path} "
358414 return 1
359415 }
360- if ($PSBoundParameters.ContainsKey (" SHA256" )) {
361- $FileHash = (Get-FileHash - Path " ${Path} " - Algorithm " SHA256" ).Hash
362- if ($FileHash -eq $SHA256 ) {
363- Show-Output " SHA256 checksum OK"
364- } else {
365- Show-Output - ForegroundColor Red " Downloaded file has an invalid SHA256 checksum. Expected: ${SHA256} , got: ${FileHash} "
366- return 1
367- }
368- } elseif ($PSBoundParameters.ContainsKey (" MD5" )) {
369- $FileHash = (Get-FileHash - Path " ${Path} " - Algorithm " MD5" ).Hash
370- if ($FileHash -eq $MD5 ) {
371- Show-Output " MD5 checksum OK"
372- } else {
373- Show-Output - ForegroundColor Red " Downloaded file has an invalid MD5 checksum. Expected: ${MD5} , got: ${FileHash} "
374- return 1
416+
417+ # Verify the file checksum if not already verified
418+ if (-not $FileAlreadyOK ) {
419+ if ($PSBoundParameters.ContainsKey (" SHA256" )) {
420+ $FileHash = (Get-FileHash - Path " ${Path} " - Algorithm " SHA256" ).Hash
421+ if ($FileHash -eq $SHA256 ) {
422+ Show-Output " SHA256 checksum OK"
423+ } else {
424+ Show-Output - ForegroundColor Red " Downloaded file has an invalid SHA256 checksum. Expected: ${SHA256} , got: ${FileHash} "
425+ return 1
426+ }
427+ } elseif ($PSBoundParameters.ContainsKey (" MD5" )) {
428+ $FileHash = (Get-FileHash - Path " ${Path} " - Algorithm " MD5" ).Hash
429+ if ($FileHash -eq $MD5 ) {
430+ Show-Output " MD5 checksum OK"
431+ } else {
432+ Show-Output - ForegroundColor Red " Downloaded file has an invalid MD5 checksum. Expected: ${MD5} , got: ${FileHash} "
433+ return 1
434+ }
375435 }
376436 }
437+
438+ # Process the downloaded file
377439 if ($File.Extension -eq " .zip" ) {
378440 if (-not $PSBoundParameters.ContainsKey (" UnzipFolderName" )) {
379441 Show-Output - ForegroundColor Red " UnzipFolderName was not provided for a zip file."
@@ -384,11 +446,12 @@ function Install-FromUri {
384446 return 1
385447 }
386448 Show-Output " Extracting ${Name} "
387- Expand-Archive - Path " ${Path} " - DestinationPath " ${Downloads} \${UnzipFolderName} "
388- Install-Executable - Name " ${Name} " - Path " ${Downloads} \${UnzipFolderName} \${UnzippedFilePath} "
449+ Expand-Archive - Path " ${Path} " - DestinationPath " ${Downloads} \${UnzipFolderName} " - Force
450+ $ExecutablePath = " ${Downloads} \${UnzipFolderName} \${UnzippedFilePath} "
389451 } else {
390- Install-Executable - Name " ${Name} " - Path " ${Downloads} \${Filename} "
452+ $ExecutablePath = " ${Downloads} \${Filename} "
391453 }
454+ Install-Executable - Name " ${Name} " - Path " ${ExecutablePath} " - BypassAuthenticode:$BypassAuthenticode
392455}
393456
394457function Install-Geekbench {
@@ -705,6 +768,42 @@ function Test-Admin {
705768 return $CurrentUser.IsInRole ([Security.Principal.WindowsBuiltinRole ]::Administrator)
706769}
707770
771+ function Test-AuthenticodeSignature {
772+ <#
773+ . SYNOPSIS
774+ Validate that a file has a valid Authenticode signature
775+ #>
776+ param (
777+ [Parameter (Mandatory = $true )][string ]$FilePath ,
778+ [switch ]$Silent = $false
779+ )
780+ $Signature = Get-AuthenticodeSignature - FilePath " ${FilePath} "
781+ $Failed = $Signature.Status -ne " Valid"
782+ if (-not $Silent ) {
783+ $Status = " Status: $ ( $Signature.Status ) ($ ( $Signature.StatusMessage ) )"
784+ if ($Failed ) {
785+ Show-Output - ForegroundColor Red " Authenticode signature verification failed for `" $ ( $Signature.Path ) `" ."
786+ Show-Output - ForegroundColor Red " ${Status} "
787+ Show-Output - ForegroundColor Red " This may be an indication of malicious tampering with the file."
788+ Show-Output - ForegroundColor Red " Please contact your system administrator or the developers of the software."
789+ } else {
790+ Show-Output " Authenticode signature verification successful for `" $ ( $Signature.Path ) `" ."
791+ Show-Output " ${Status} , Signature type: $ ( $Signature.SignatureType ) "
792+ }
793+ $Signer = $Signature.SignerCertificate
794+ if ($Signer ) {
795+ Show-Output " Certificate: $ ( Get-CertificateInfo $Signer ) "
796+ }
797+ $TimeStamper = $Signature.TimeStamperCertificate
798+ if ($TimeStamper ) {
799+ Show-Output " Timestamper: $ ( Get-CertificateInfo $TimeStamper ) "
800+ }
801+ }
802+ if ($Failed ) {
803+ throw " Authenticode signature verification failed for `" $ ( $Signature.Path ) `" ."
804+ }
805+ }
806+
708807function Test-CommandExists {
709808 [Diagnostics.CodeAnalysis.SuppressMessageAttribute (" PSUseSingularNouns" , " " , Justification= " Not plural" )]
710809 [OutputType ([bool ])]
0 commit comments