Skip to content

Instantly share code, notes, and snippets.

@Smalls1652
Last active December 16, 2021 03:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Smalls1652/8148c60f6360d602f2f3f012176545eb to your computer and use it in GitHub Desktop.
Save Smalls1652/8148c60f6360d602f2f3f012176545eb to your computer and use it in GitHub Desktop.
Installer/updater for the Sysinternals Suite.

Sysinternals Suite Installer/Updater

Install and/or update the Sysinternals Suite in your user profile.

Release Notes

2021.12.00

  • Initial Release

Exmaples

Example 01

Install Sysinternals to ~\.sysinternals\ and add the folder to the PATH variable.

.\Invoke-SysinternalsInstall.ps1 -AddToUserPathEnvironmentVariable

Example 02

Install Sysinternals to ~\tools\sysinternals\ and do not update the PATH variable.

.\Invoke-SysInternalsInstall.ps1 -InstallPath "~\tools\sysinternals\"
<#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."
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment