|
|
|
<#PSScriptInfo |
|
|
|
.VERSION 2021.12.0 |
|
|
|
.GUID 4717f1e9-c5ac-4343-bc80-49aee14a3904 |
|
|
|
.AUTHOR Tim Small |
|
|
|
.COMPANYNAME Smalls.Online |
|
|
|
.COPYRIGHT 2021 |
|
|
|
.TAGS |
|
|
|
.LICENSEURI |
|
|
|
.PROJECTURI |
|
https://gist.github.com/Smalls1652/8148c60f6360d602f2f3f012176545eb |
|
|
|
.ICONURI |
|
|
|
.EXTERNALMODULEDEPENDENCIES |
|
|
|
.REQUIREDSCRIPTS |
|
|
|
.EXTERNALSCRIPTDEPENDENCIES |
|
|
|
.RELEASENOTES |
|
2021.12.00: |
|
- Initial release |
|
|
|
.PRIVATEDATA |
|
|
|
#> |
|
|
|
<# |
|
.SYNOPSIS |
|
Install the Sysinternals Suite. |
|
.DESCRIPTION |
|
Install and/or update the Sysinternals Suite in your user profile. |
|
.PARAMETER InstallPath |
|
The path to install Sysinternals. (Defaults to '~\.sysinternals\') |
|
.PARAMETER AddToUserPathEnvironmentVariable |
|
Whether to add the install to the current user's PATH environment variable. |
|
.EXAMPLE |
|
PS C:\> .\Invoke-SysinternalsInstall.ps1 -AddToUserPathEnvironmentVariable |
|
|
|
Installs Sysinternals to '~\.sysinternals\' and adds the folder to the PATH variable. |
|
.EXAMPLE |
|
PS C:\> .\Invoke-SysInternalsInstall.ps1 -InstallPath "~\tools\sysinternals\" |
|
|
|
Install Sysinternals to '~\tools\sysinternals\' and do not update the PATH variable. |
|
#> |
|
[CmdletBinding()] |
|
param( |
|
[Parameter(Position = 0)] |
|
[string]$InstallPath, |
|
[Parameter(Position = 1)] |
|
[switch]$AddToUserPathEnvironmentVariable |
|
) |
|
|
|
class SysinternalsShouldInstall { |
|
[bool]$ShouldInstall |
|
[string]$Reason |
|
|
|
SysinternalsShouldInstall() {} |
|
|
|
SysinternalsShouldInstall([bool]$_shouldInstall, [string]$_reason) { |
|
$this.ShouldInstall = $_shouldInstall |
|
$this.Reason = $_reason |
|
} |
|
} |
|
|
|
function updateEnvironmentPathVar { |
|
[CmdletBinding()] |
|
param() |
|
|
|
$env:Path = "$([System.Environment]::GetEnvironmentVariable("Path","Machine"));$([System.Environment]::GetEnvironmentVariable("Path","User"))" |
|
} |
|
|
|
# Set variables that will be used later on. |
|
$dateTimeStringFormat = "yyyy-MM-dd HH:mm:ss zzz" |
|
$sysinternalsSuiteDownloadUri = "https://download.sysinternals.com/files/SysinternalsSuite.zip" |
|
|
|
# Check to see if the 'InstallPath' parameter is null or not. |
|
# !!! Note: |
|
# For some reason the '$null -eq $InstallPath' logic didn't work for some reason, |
|
# so I'm using the 'IsNullOrEmpty()' method in 'System.String'. |
|
$finalInstallPath = $null |
|
$dirDoesNotExist = $false |
|
if ([string]::IsNullOrEmpty($InstallPath)) { |
|
# If 'InstallPath' is null, then set the final install path to 'path\to\userprofile\.sysinternals'. |
|
# The path is built by getting the user profile path from current user's environment variables. |
|
$finalInstallPath = Join-Path -Path ([System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::UserProfile)) -ChildPath ".sysinternals\" |
|
|
|
if ((Test-Path -Path $finalInstallPath) -eq $false) { |
|
# If the install path doesn't exist, set 'dirDoesNotExist' to true. |
|
$dirDoesNotExist = $true |
|
} |
|
} |
|
else { |
|
# If 'InstallPath' is not null, then try to resolve the path. |
|
try { |
|
$finalInstallPath = (Resolve-Path -Path $InstallPath -ErrorAction "Stop").Path |
|
} |
|
catch [System.Management.Automation.ItemNotFoundException] { |
|
# If the path wasn't resolved, then set 'dirDoesNotExist' to true |
|
# and set the final install path to what was provided in 'InstallPath'. |
|
$dirDoesNotExist = $true |
|
$finalInstallPath = $InstallPath |
|
} |
|
catch { |
|
# If 'Resolve-Path' encountered any other error, then terminate with that error. |
|
$errorDetails = $PSItem |
|
$PSCmdlet.ThrowTerminatingError($errorDetails) |
|
} |
|
|
|
if (($dirDoesNotExist -eq $false) -and ((Get-Item -Path $finalInstallPath).Attributes -ne [System.IO.FileAttributes]::Directory)) { |
|
# If the path has been resolved successfully and the item is not a directory, |
|
# then throw a terminating error. |
|
$PSCmdlet.ThrowTerminatingError( |
|
[System.Management.Automation.ErrorRecord]::new( |
|
[System.IO.IOException]::new("The install path specified is not a directory."), |
|
"InstallPathNotDir", |
|
[System.Management.Automation.ErrorCategory]::InvalidOperation, |
|
$finalInstallPath |
|
) |
|
) |
|
} |
|
} |
|
|
|
Write-Verbose "Install path: $($finalInstallPath)" |
|
Write-Verbose "Install path does not exist: $($dirDoesNotExist)" |
|
|
|
if ($dirDoesNotExist -eq $true) { |
|
# Create the install path, if it doesn't exist. |
|
$finalInstallPath = (New-Item -Path $finalInstallPath -ItemType "Directory" -ErrorAction "Stop").FullName |
|
} |
|
|
|
# Create a path to the '.version-info' file in the install path. |
|
$installedVerDateTimeFilePath = Join-Path -Path $finalInstallPath -ChildPath ".version-info" |
|
|
|
# Create the HTTP client. |
|
$httpClient = [System.Net.Http.HttpClient]::new() |
|
|
|
# Getting the HTTP Content Headers for the currently available Sysinternals Suite. |
|
# This will allow us to check if the current locally downloaded version is up-to-date or not without actually downloading it. |
|
Write-Verbose "Getting the currently available version's last modified datetime." |
|
$downloadOnlyHeadersReqMsg = [System.Net.Http.HttpRequestMessage]::new( |
|
[System.Net.Http.HttpMethod]::Head, |
|
$sysinternalsSuiteDownloadUri |
|
) |
|
$downloadOnlyHeadersRsp = $httpClient.SendAsync($downloadOnlyHeadersReqMsg, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).GetAwaiter().GetResult() |
|
|
|
# Get the current version of the Sysinternals Suite. |
|
# This is parsed from the 'Last-Modified' header in the response. |
|
$currentVerDateTime = $downloadOnlyHeadersRsp.Content.Headers.LastModified |
|
$currentVerDateTimeString = $currentVerDateTime.UtcDateTime.ToString($dateTimeStringFormat) |
|
|
|
# Dispose the request message and response message. |
|
$downloadOnlyHeadersRsp.Dispose() |
|
$downloadOnlyHeadersReqMsg.Dispose() |
|
|
|
# Start the check to see if Sysinternal should be installed/updated. |
|
$shouldInstall = $null |
|
if ((Test-Path -Path $installedVerDateTimeFilePath) -eq $true) { |
|
# If the '.version-info' file exists, get the contents of it and convert it to the 'System.DateTime' type. |
|
Write-Verbose "Checking to see if the installed version is out-of-date." |
|
$installedVerDateTime = [datetime]::Parse((Get-Content -Path $installedVerDateTimeFilePath -Raw)) |
|
|
|
if ($currentVerDateTime -ne $installedVerDateTime) { |
|
# If the current version doesn't equal the installed version, |
|
# then set 'shouldInstall' to true and supply "Out of date" as the reason. |
|
$shouldInstall = [SysinternalsShouldInstall]::new($true, "Out of date") |
|
} |
|
else { |
|
# If the current version does equal the installed version, |
|
# then set 'shouldInstall' to false and supply "up-to-date" as the reason. |
|
$shouldInstall = [SysinternalsShouldInstall]::new($false, "Up-to-date") |
|
} |
|
} |
|
else { |
|
# If the '.version-info' file does not exist, |
|
# then set 'shouldInstall' to true and supply "Not installed" as the reason. |
|
# When this file doesn't exist, then Sysinternals wasn't installed through this method or has never been installed. |
|
$shouldInstall = [SysinternalsShouldInstall]::new($true, "Not installed") |
|
} |
|
|
|
if ($shouldInstall.ShouldInstall -eq $false) { |
|
# If 'shouldInstall' is false, then do nothing. |
|
Write-Warning "The installed version of Sysinternals is up-to-date." |
|
} |
|
else { |
|
# If 'shouldInstall' is true, then start the install process. |
|
Write-Warning "Sysinternals will be installed for this reason: $($shouldInstall.Reason)." |
|
|
|
if ($dirDoesNotExist -eq $false) { |
|
# If the install path was set as already existing, |
|
# remove all the current files in that directory. |
|
Write-Verbose "Removing all currently installed files." |
|
$installedItems = Get-ChildItem -Path $finalInstallPath |
|
foreach ($installedItem in $installedItems) { |
|
if ($installedItem.Attributes -eq [System.IO.FileAttributes]::Directory) { |
|
Remove-Item -Path $installedItem -Force -Recurse -Verbose:$false |
|
} |
|
else { |
|
Remove-Item -Path $installedItem -Force -Verbose:$false |
|
} |
|
} |
|
} |
|
|
|
# Create the request message to download the ZIP file and start the download. |
|
$downloadReqMsg = [System.Net.Http.HttpRequestMessage]::new( |
|
[System.Net.Http.HttpMethod]::Get, |
|
$sysinternalsSuiteDownloadUri |
|
) |
|
$downloadReqMsg.Headers.Accept.Add([System.Net.Http.Headers.MediaTypeWithQualityHeaderValue]::Parse("application/x-zip-compressed")) |
|
|
|
Write-Verbose "Downloading the Sysinternals Suite ZIP file from '$($sysinternalsSuiteDownloadUri)'." |
|
$downloadRsp = $httpClient.SendAsync($downloadReqMsg).GetAwaiter().GetResult() |
|
|
|
# Define the temporary output paths for the scratch directory, the ZIP file, and the unzipped contents of the ZIP file. |
|
$tmpOutputDirPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.IO.Path]::GetRandomFileName()) |
|
$tmpOutputZipOutPath = Join-Path -Path $tmpOutputDirPath -ChildPath "SysinternalsSuite.zip" |
|
$tmpOutputZipExpandedPath = Join-Path -Path $tmpOutputDirPath -ChildPath "SysinternalsSuite\" |
|
|
|
# Create the scratch directory in the user's temp directory. |
|
$null = New-Item -Path $tmpOutputDirPath -ItemType "Directory" |
|
|
|
# Copy the contents of the downloaded ZIP to the scratch directory. |
|
$zipOutFile = [System.IO.File]::Create($tmpOutputZipOutPath) |
|
$downloadRsp.Content.ReadAsStream().CopyToAsync($zipOutFile).GetAwaiter().GetResult() |
|
|
|
# Dispose the FileStream object of the ZIP file, download request/response message objects, and the HTTP client. |
|
$zipOutFile.Dispose() |
|
$downloadReqMsg.Dispose() |
|
$downloadRsp.Dispose() |
|
$httpClient.Dispose() |
|
|
|
# Unzip the ZIP file. |
|
Write-Verbose "Unzipping the ZIP file." |
|
$ProgressPreference = "SilentlyContinue" |
|
$null = Expand-Archive -Path $tmpOutputZipOutPath -DestinationPath $tmpOutputZipExpandedPath -Verbose:$false |
|
$ProgressPreference = "Continue" |
|
|
|
# Copy each file to the install directory. |
|
$itemsInExpandedDir = Get-ChildItem -Path $tmpOutputZipExpandedPath |
|
foreach ($fileItem in $itemsInExpandedDir) { |
|
Write-Verbose "Copying $($fileItem.Name) -> $($finalInstallPath)" |
|
|
|
$fileDstPath = Join-Path -Path $finalInstallPath -ChildPath $fileItem.Name |
|
Copy-Item -Path $fileItem.FullName -Destination $fileDstPath -Recurse -Force -Verbose:$false |
|
} |
|
|
|
# Save the current version's datetime to the install directory. |
|
Write-Verbose "Saving version datetime for this install." |
|
Write-Verbose "Version: $($currentVerDateTimeString)" |
|
$currentVerDateTimeString | Out-File -FilePath $installedVerDateTimeFilePath -Force |
|
|
|
# Remove the scratch directory. |
|
Write-Verbose "Cleaning up temporary files." |
|
Remove-Item -Path $tmpOutputDirPath -Force -Recurse |
|
} |
|
|
|
if ($AddToUserPathEnvironmentVariable -eq $true) { |
|
# If the 'AddToUserPathEnvironmentVariable' switch parameter was provided, |
|
# then start the process of adding the install path to the current user's PATH environment variable. |
|
|
|
# Get the current items in the PATH variable. |
|
Write-Verbose "Getting the items in the current user's PATH environment variable." |
|
$currentPathVarItems = [System.Collections.Generic.List[string]]::new((Get-ItemPropertyValue -Path "HKCU:\Environment\" -Name "Path").Split(";")) |
|
|
|
$trimmedInstallPath = [System.IO.Path]::TrimEndingDirectorySeparator($finalInstallPath) |
|
if ($trimmedInstallPath -notin $currentPathVarItems) { |
|
# If the install path is not in the PATH variable, then add it in. |
|
|
|
Write-Warning "'$($trimmedInstallPath)' is not in the current user's PATH environment variable. Adding..." |
|
$currentPathVarItems.Add($trimmedInstallPath) |
|
|
|
$null = Set-ItemProperty -Path "HKCU:\Environment\" -Name "Path" -Value ([string]::Join(";", $currentPathVarItems)) |
|
|
|
# Refresh the current PowerShell session's definition of the PATH variable with the newly added contents. |
|
Write-Verbose "Refreshing the current session's PATH environment variable." |
|
updateEnvironmentPathVar |
|
} |
|
else { |
|
# If the install path is already in the PATH variable, then do nothing. |
|
Write-Verbose "'$($trimmedInstallPath)' is already in the current user's PATH environment variable." |
|
} |
|
} |