-
Notifications
You must be signed in to change notification settings - Fork 44
Improved exit code with errors, improved service detection logic, additional logging for Support #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+137
−114
Merged
Improved exit code with errors, improved service detection logic, additional logging for Support #146
Changes from 3 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
cedcc3b
Update InstallHuntress.powershellv2.ps1
Alan-Huntress 3688eba
Codacy fixes
Alan-Huntress 6a88770
Codacy fix for empty try/catch
Alan-Huntress ddaf36b
Wider encompassing search for Visual C++
Alan-Huntress 77fab86
Updated VC++ logging with all of the relevant details
Alan-Huntress e08dc24
Following automated code checkers suggestions
Alan-Huntress b28978e
Logging improvements, fix Write-InstallScriptInfo execution
Alan-Huntress b3a215a
Formatting glow up
Alan-Huntress 0475c50
Updated URLs for testing network connectivity
Alan-Huntress File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,7 @@ | |
| # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| # | ||
| # Authors: Alan Bishop, Sharon Martin, John Ferrell, Dave Kleinatland, Cameron Granger | ||
| # Authors: Alan Bishop, Sharon Martin, John Ferrell, Dave Kleinatland, Evan Shewchuk | ||
|
|
||
|
|
||
| # The Huntress installer needs an Account Key and an Organization Key (a user specified name or description) which is used to affiliate an Agent with a | ||
|
|
@@ -72,7 +72,7 @@ | |
| ############################################################################## | ||
|
|
||
| # These are used by the Huntress support team when troubleshooting. | ||
| $ScriptVersion = "Version 2, major revision 8, 2025 Oct 13" | ||
| $ScriptVersion = "Version 2, major revision 8, 2025 Dec 22" | ||
| $ScriptType = "PowerShell" | ||
|
|
||
| # variables used throughout this script | ||
|
|
@@ -82,7 +82,6 @@ | |
| $InstallerPath = Join-Path $Env:TMP $InstallerName | ||
| $HuntressKeyPath = "HKLM:\SOFTWARE\Huntress Labs\Huntress" | ||
| $HuntressRegKey = "HKLM:\SOFTWARE\Huntress Labs" | ||
| $ScriptFailed = "Script Failed!" | ||
| $SupportMessage = "Please send the error message to [email protected]" | ||
| $HuntressAgentServiceName = "HuntressAgent" | ||
| $HuntressUpdaterServiceName = "HuntressUpdater" | ||
|
|
@@ -187,33 +186,18 @@ | |
|
|
||
| # Ensure we have an account key (hard coded or passed params) and that it's in the correct form | ||
| if ($AccountKey -eq "__ACCOUNT_KEY__") { | ||
| $err = "AccountKey not set! Suggest using the -acctkey flag followed by your account key (you can find it in the Downloads section of your Huntress portal)." | ||
| LogMessage $err | ||
| throw $ScriptFailed + " " + $err | ||
| exit 1 | ||
| copyLogAndExit -throwError "AccountKey not set! Suggest using the -acctkey flag followed by your account key (you can find it in the Downloads section of your Huntress portal)." | ||
| } elseif ($AccountKey.length -ne 32) { | ||
| $err = "Invalid AccountKey specified (incorrect length)! Suggest double checking the key was copy/pasted in its entirety. Length = $($AccountKey.length) expected value = 32" | ||
| LogMessage $err | ||
| throw $ScriptFailed + " " + $err | ||
| exit 1 | ||
| copyLogAndExit -throwError "Invalid AccountKey specified (incorrect length)! Suggest double checking the key was copy/pasted in its entirety. Length = $($AccountKey.length) expected value = 32" | ||
| } elseif (($AccountKey -match '[^a-zA-Z0-9]')) { | ||
| $err = "Invalid AccountKey specified (invalid characters found)! Suggest double checking the key was copy/pasted fully" | ||
| LogMessage $err | ||
| throw $ScriptFailed + " " + $err | ||
| exit 1 | ||
| copyLogAndExit -throwError "Invalid AccountKey specified (invalid characters found)! Suggest double checking the key was copy/pasted fully" | ||
| } | ||
|
|
||
| # Ensure we have an organization key (hard coded or passed params). | ||
| if ($OrganizationKey -eq "__ORGANIZATION_KEY__") { | ||
| $err = "OrganizationKey not specified! This is a user defined identifier set by you (usually your customer's organization name)" | ||
| LogMessage $err | ||
| throw $ScriptFailed + " " + $err | ||
| exit 1 | ||
| copyLogAndExit -throwError "OrganizationKey not specified! This is a user defined identifier set by you (usually your customer's organization name)" | ||
| } elseif ($OrganizationKey.length -lt 1) { | ||
| $err = "Invalid OrganizationKey specified (length should be > 0)!" | ||
| LogMessage $err | ||
| throw $ScriptFailed + " " + $err | ||
| exit 1 | ||
| copyLogAndExit -throwError "Invalid OrganizationKey specified (length should be > 0)!" | ||
| } | ||
| LogMessage "Parameters verified." | ||
| } | ||
|
|
@@ -280,12 +264,20 @@ | |
| function StopHuntressServices { | ||
| LogMessage "Stopping Huntress services..." | ||
| if (Confirm-ServiceExists($HuntressAgentServiceName)) { | ||
| Stop-Service -Name "$HuntressAgentServiceName" | ||
| try { | ||
| Stop-Service -Name "$HuntressAgentServiceName" -ErrorAction SilentlyContinue | ||
| } catch { | ||
| LogMessage "Unable to stop HuntressAgent, possible Tamper Protection interference." | ||
| } | ||
| } else { | ||
| LogMessage "$($HuntressAgentServiceName) not found, nothing to stop" | ||
| } | ||
| if (Confirm-ServiceExists($HuntressUpdaterServiceName)) { | ||
| Stop-Service -Name "$HuntressUpdaterServiceName" | ||
| try { | ||
| Stop-Service -Name "$HuntressUpdaterServiceName" -ErrorAction SilentlyContinue | ||
| } catch { | ||
| LogMessage "Unable to stop HuntressUpdater, possible Tamper Protection interference." | ||
| } | ||
| } else { | ||
| LogMessage "$($HuntressUpdaterServiceName) not found, nothing to stop" | ||
| } | ||
|
|
@@ -297,12 +289,7 @@ | |
| try { | ||
| $varChain.Build((Get-AuthenticodeSignature -FilePath "$file").SignerCertificate) | out-null | ||
| } catch [System.Management.Automation.MethodInvocationException] { | ||
| $err = ( "ERROR: '$file' did not contain a valid digital certificate. " + | ||
| "Something may have corrupted/modified the file during the download process. " + | ||
| "Suggest trying again, contact [email protected] if it fails >2 times") | ||
| LogMessage $err | ||
| LogMessage $SupportMessage | ||
| throw $ScriptFailed + " " + $err + " " + $SupportMessage | ||
| copyLogAndExit -throwError "ERROR: '$file' did not contain a valid digital certificate, something may have corrupted the file. Try again and contact Support if the 2nd attempt fails" | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -337,11 +324,7 @@ | |
| # Rather than check for 'Tls12', we force-set TLS 1.2 and catch the error if it's truly unsupported. | ||
| [Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([Net.SecurityProtocolType], 3072) | ||
| } catch { | ||
| $msg = $_.Exception.Message | ||
| $err = "ERROR: Unable to use a secure version of TLS. Please verify Hotfix KB3140245 is installed." | ||
| LogMessage $msg | ||
| LogMessage $err | ||
| throw $ScriptFailed + " " + $msg + " " + $err | ||
| copyLogAndExit -throwError "ERROR: Unable to use a secure version of TLS. Please verify Hotfix KB3140245 is installed. Error: ($_.Exception.Message)" | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -361,19 +344,15 @@ | |
| $WebClient.DownloadFile($DownloadURL, $InstallerPath) | ||
| break | ||
| } catch { | ||
| $msg = $_.Exception.Message | ||
| $err = "WARNING: Failed to download the Huntress Installer ($attempt/$attempts), retrying in $delay seconds." | ||
| LogMessage $msg | ||
| $err = "WARNING: Failed to download the Huntress Installer ($attempt/$attempts), retrying in $delay seconds. Error: $_.Exception.Message" | ||
| LogMessage $err | ||
| Start-Sleep -Seconds $delay | ||
| } | ||
| } | ||
|
|
||
| # Ensure the file downloaded correctly, if not, throw error | ||
| if ( ! (Test-Path $InstallerPath) ) { | ||
| $err = "ERROR: Failed to download the Huntress Installer. Try accessing $($DownloadURL) from the host where the download failed. Please contact [email protected] if the problem persists." | ||
| LogMessage $err | ||
| throw $ScriptFailed + " " + $err | ||
| copyLogAndExit -throwError "ERROR: Failed to download the Huntress Installer. Try accessing $($DownloadURL) from the host where the download failed. Please contact [email protected] if the problem persists." | ||
| } | ||
|
|
||
| $msg = "Installer downloaded to '$InstallerPath'..." | ||
|
|
@@ -385,13 +364,12 @@ | |
| # check that the installer downloaded and wasn't quarantined | ||
| LogMessage "Checking for installer '$InstallerPath'..." | ||
| if ( ! (Test-Path $InstallerPath) ) { | ||
| $err = "ERROR: The installer was unexpectedly removed from $InstallerPath" | ||
| $msg = ($err + "`n"+ | ||
| $err = ("ERROR: The installer was unexpectedly removed from $InstallerPath `n"+ | ||
| "A security product may have quarantined the installer. Please check " + | ||
| "your logs. If the issue continues to occur, please send the log to the Huntress " + | ||
| "Team for help at [email protected]") | ||
| LogMessage $msg | ||
| throw $ScriptFailed + " " + $err + " " + $SupportMessage | ||
| LogMessage $err | ||
| copyLogAndExit -throwError $err | ||
| } | ||
|
|
||
| # verify the installer's integrity | ||
|
|
@@ -410,10 +388,7 @@ | |
| $process | Wait-Process -Timeout $timeout -ErrorAction Stop | ||
| } catch { | ||
| $process | Stop-Process -Force | ||
| $err = "ERROR: Installer failed to complete in $timeout seconds. Possible interference from a security product?" | ||
| LogMessage $err | ||
| LogMessage $SupportMessage | ||
| throw $ScriptFailed + " " + $err + " " + $SupportMessage | ||
| copyLogAndExit -throwError "ERROR: Installer failed to complete in $timeout seconds. Possible interference from a security product?" | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -464,10 +439,7 @@ | |
| # Ensure the critical files were created. | ||
| foreach ( $file in ($HuntressAgentPath, $HuntressUpdaterPath, $hUpdaterPath) ) { | ||
| if ( ! (Test-Path $file) ) { | ||
| $err = "ERROR: $file did not exist. Check your AV/security software quarantine" | ||
| LogMessage $err | ||
| LogMessage $SupportMessage | ||
| throw $ScriptFailed + " " + $err + " " + $SupportMessage | ||
| copyLogAndExit -throwError "ERROR: $file did not exist. Check your AV/security software quarantine" | ||
| } | ||
| LogMessage "'$file' is present." | ||
| } | ||
|
|
@@ -492,8 +464,7 @@ | |
| LogMessage "New install detected. It may take 24 hours for Huntress EDR (Rio) to install!" | ||
| } | ||
| } else { | ||
| LogMessage "$($svc) service is missing! $($SupportMessage)" | ||
| throw "$($ScriptFailed) $($svc) service is missing! + $($SupportMessage)" | ||
| copyLogAndExit -throwError "$($svc) service is missing! + $($SupportMessage)" | ||
| } | ||
| } | ||
| # check if the service is running, attempt to restart if not (only for base agent). | ||
|
|
@@ -504,7 +475,7 @@ | |
| LogMessage "ERROR: The $($svc) service is not running. Attempting to restart" | ||
| Start-Service $svc | ||
| if (! (Confirm-ServiceRunning($svc))) { | ||
| throw "$($ScriptFailed) ERROR: restart of service $($svc) failed. $($SupportMessage)" | ||
| copyLogAndExit -throwError "ERROR: restart of service $($svc) failed." | ||
| } | ||
| } else { | ||
| LogMessage "'$svc' is running." | ||
|
|
@@ -519,20 +490,14 @@ | |
| } else { | ||
| # Ensure the Huntress registry key is present. | ||
| if ( ! (Test-Path $HuntressKeyPath) ) { | ||
| $err = "ERROR: The registry key '$HuntressKeyPath' did not exist. You may need to reinstall with the -reregister flag" | ||
| LogMessage $err | ||
| LogMessage $SupportMessage | ||
| throw $ScriptFailed + " " + $err + " " + $SupportMessage | ||
| copyLogAndExit -throwError "ERROR: The registry key '$HuntressKeyPath' did not exist. You may need to reinstall with the -reregister flag" | ||
| } | ||
|
|
||
| # Ensure the Huntress registry values are present. | ||
| $HuntressKeyObject = Get-ItemProperty $HuntressKeyPath | ||
| foreach ( $value in ($AgentIdKeyValueName, $OrganizationKeyValueName, $TagsValueName) ) { | ||
| If ( ! (Get-Member -inputobject $HuntressKeyObject -name $value -Membertype Properties) ) { | ||
| $err = "ERROR: The registry value $value did not exist within $HuntressKeyPath. You may need to reinstall with the -reregister flag" | ||
| LogMessage $err | ||
| LogMessage $SupportMessage | ||
| throw $ScriptFailed + " " + $err + " " + $SupportMessage | ||
| copyLogAndExit -throwError "ERROR: The registry value $value did not exist within $HuntressKeyPath. You may need to reinstall with the -reregister flag" | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -542,10 +507,7 @@ | |
| LogMessage "WARNING: Can't verify agent registration due to 32bit PowerShell on 64bit host." | ||
| } else { | ||
| If ($HuntressKeyObject.$AgentIdKeyValueName -eq 0) { | ||
| $err = ("ERROR: The agent did not register. Check the log (%ProgramFiles%\Huntress\HuntressAgent.log) for errors.") | ||
| LogMessage $err | ||
| LogMessage $SupportMessage | ||
| throw $ScriptFailed + " " + $err + " " + $SupportMessage | ||
| copyLogAndExit -throwError "ERROR: The agent did not register. Check the log (%ProgramFiles%\Huntress\HuntressAgent.log) for errors. Missing $($HuntressKeyObject.$AgentIdKeyValueName)" | ||
| } | ||
| LogMessage "Agent registered." | ||
| } | ||
|
|
@@ -649,8 +611,7 @@ | |
| } | ||
|
|
||
| $err = "ERROR: $($name) running as '$($process) $($flags)' failed to complete in $timeout seconds, full error message: '$($msg).'" | ||
| LogMessage $err | ||
| copyLogAndExit | ||
| copyLogAndExit -throwError $err | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -727,7 +688,7 @@ | |
| # if Huntress services still exist, then delete | ||
| $services = @("HuntressRio", "HuntressAgent", "HuntressUpdater", "Huntmon") | ||
| foreach ($service in $services) { | ||
| if ( $service ) { | ||
| if (Get-Service -name $service -ErrorAction SilentlyContinue) { | ||
| LogMessage "Service $($service) detected post uninstall, attempting to remove" | ||
| c:\Windows\System32\sc.exe STOP $service | ||
| c:\Windows\System32\sc.exe DELETE $service | ||
|
|
@@ -811,14 +772,12 @@ | |
| # Convert from bytes, if necessary | ||
| if ($Response.Content.GetType() -eq [System.Byte[]]) { | ||
| $StrContent = [System.Text.Encoding]::UTF8.GetString($Response.Content) | ||
| }else { | ||
| } else { | ||
| $StrContent = $Response.Content.ToString().Trim() | ||
| } | ||
|
|
||
| # Remove all newlines from the content | ||
| $StrContent = [string]::join("",($StrContent.Split("`n"))) | ||
|
|
||
|
|
||
| $ContentMatch = $StrContent -eq "96bca0cef10f45a8f7cf68c4485f23a4" | ||
| } catch { | ||
| LogMessage "Error: $($_.Exception.Message)" | ||
|
|
@@ -937,28 +896,68 @@ | |
| if ( $areURLsAvailable) { | ||
| LogMessage "Network Connectivity verified!" | ||
| } else { | ||
| copyLogAndExit | ||
| copyLogAndExit -throwError "ERROR: Network connectivity problem detected!" | ||
| } | ||
|
|
||
| # Search both 64-bit and 32-bit uninstall registry paths for the required C++ library (version 12+ recommended) | ||
| $installedApps = (Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) | ||
| $count = 0 | ||
| foreach ( $app in $installedApps) { | ||
| try { | ||
| if ( $app.DisplayName -like "*vc++*") { | ||
| LogMessage "Name: $($app.DisplayName) Version: $($app.DisplayVersion)" | ||
| count++ | ||
| } | ||
| } catch { | ||
| # do nothing for blank entries | ||
| } | ||
| } | ||
| if ( $count -eq 0 ) { | ||
| LogMessage "Warning: Visual C++ library not found! You may have issues with Huntress running." + | ||
| " Highly recommend installing C++ and trying the deploy again: https://www.microsoft.com/en-us/download/details.aspx?id=48145" | ||
| } | ||
| } | ||
|
|
||
| # DebugLog contains useful info not found in surveys, so copy to Huntress folder for higher visibility with future troubleshooting AB | ||
| # In the past we copied to the users temp folder, difficult to find on machines with lots of profiles. Solved this by always placing the log in the normal Huntress folder. | ||
| # This function copies the Huntress DebugLog to a more permanent location as it's incredibly helpful for troubleshooting. | ||
| # Exits with a code 0 if $throwError wasn't passed, otherwise throws the error contained in the $throwError string | ||
| function copyLogAndExit { | ||
| param ( | ||
| [string]$throwError | ||
| ) | ||
|
|
||
| if ( $null -eq $throwError ) { | ||
| $throwError="0" | ||
| } | ||
|
|
||
| # log the error message first | ||
| if ($throwError -ne "0") { | ||
| LogMessage "WARNING: Script errors detected, operation may not have completed! $throwError `n$SupportMessage" | ||
| } | ||
|
|
||
| # sleep to ensure file operations have completed | ||
| Start-Sleep 1 | ||
| $agentPath = getAgentPath | ||
| $logLocation = Join-Path $agentPath "HuntressPoShInstaller.log" | ||
|
|
||
| # If this is an unistall, we'll leave the log in the C:\temp dir otherwise, | ||
| # we'll copy the log to the huntress directory | ||
| # If this is an unistall, we'll leave the log in the C:\temp dir, otherwise copy the log to the huntress directory | ||
| if (!$uninstall){ | ||
| if (!(Test-Path -path $agentPath)) {New-Item $agentPath -Type Directory} | ||
| Copy-Item -Path $DebugLog -Destination $logLocation -Force | ||
| Write-Output "'$($DebugLog)' copied to '$logLocation'." | ||
| try { | ||
| Copy-Item -Path $DebugLog -Destination $logLocation -Force -ErrorAction SilentlyContinue | ||
| Write-Output "'$($DebugLog)' copied to '$logLocation'." | ||
| } catch { | ||
| Write-Output "Unable to copy Installer log, possible Tamper Protection interference. Look in \Windows\temp\ for HuntressPoShInstaller.log" | ||
| } | ||
| } | ||
|
|
||
| Write-Output "Script complete" | ||
| exit 0 | ||
| # if no error was passed, exit gracefully, otherwise throw an error and exit | ||
| if ($error -eq "0") { | ||
| Write-Output "Script complete!" | ||
| exit 0 | ||
| } else { | ||
| Write-Output "WARNING: Script errors detected, operation may not have completed! Error: [$throwError]" | ||
| throw $throwError | ||
| } | ||
| } | ||
|
|
||
| # Sometimes previous installs can be stuck with services in the Disabled state, this function attempts to set the state to Automatic. | ||
|
|
@@ -1081,7 +1080,7 @@ | |
| LogMessage "powershell -executionpolicy bypass -f ./InstallHuntress.powershellv2.ps1 [-acctkey <account_key>] [-orgkey <organization_key>] [-tags <tags>] [-reregister] [-reinstall] [-uninstall] `n" | ||
| LogMessage "Example:" | ||
| LogMessage 'powershell -executionpolicy bypass -f ./InstallHuntress.powershellv2.ps1 -acctkey "0b8a694b2eb7b642069" -orgkey "Buzzword Company Name" -tags "production,US West" ' | ||
| copyLogAndExit | ||
| copyLogAndExit -throwError "No flags or account key found! Exiting." | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -1124,8 +1123,7 @@ | |
| $assetCount = (Get-ChildItem -Path $agentPath -File | Measure-Object).count | ||
| # to avoid issues with a single file blocking installs, only exit script if multiple files are found and script not run with -reregister or -reinstall | ||
| if ($assetCount -gt 1) { | ||
| LogMessage "The Huntress Agent is already installed in $agentPath. Exiting with no changes. Suggest using -reregister or -reinstall flags. Asset count = $assetCount" | ||
| copyLogAndExit | ||
| copyLogAndExit -throwError "The Huntress Agent is already installed in $agentPath. Exiting with no changes. Suggest using -reregister or -reinstall flags. Asset count = $assetCount" | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -1142,9 +1140,7 @@ | |
| main | ||
| Write-InstallScriptInfo | ||
| } catch { | ||
| $ErrorMessage = $_.Exception.Message | ||
| LogMessage $ErrorMessage | ||
| copyLogAndExit | ||
| copyLogAndExit -throwError $_.Exception.Message | ||
| } | ||
|
|
||
| LogMessage "Script Complete" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.