diff --git a/AMPs/Agent AD Status.amp b/AMPs/Agent AD Status.amp new file mode 100644 index 0000000..8ddf7bd --- /dev/null +++ b/AMPs/Agent AD Status.amp @@ -0,0 +1,158 @@ + + + + + + 4a9980e8-bcbe-4987-a77e-80435f92de5c.PartnerConfigFileVersion + OutputObject + + PartnerConfigFileVersion + + + 4a9980e8-bcbe-4987-a77e-80435f92de5c.NCentralVersion + OutputObject + + NCentralVersion + + + 4a9980e8-bcbe-4987-a77e-80435f92de5c.InstallationFile + OutputObject + + InstallationFile + + + 4a9980e8-bcbe-4987-a77e-80435f92de5c.CustomerID + OutputObject + + CustomerID + + + 4a9980e8-bcbe-4987-a77e-80435f92de5c.RegistrationToken + OutputObject + + RegistrationToken + + + 4a9980e8-bcbe-4987-a77e-80435f92de5c.GPOInstalled + OutputObject + + GPOInstalled + + + + + + + + + 562,1761 + Assembly references and imported namespaces serialized as XML namespaces + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [NetworkFolder] + + + + + [GPOName] + + + + + [sysvol] + + + + + + + [RunPowerShellScript_PartnerConfigFileVersion] + + + + + [RunPowerShellScript_NCentralVersion] + + + + + [RunPowerShellScript_InstallationFile] + + + + + [RunPowerShellScript_CustomerID] + + + + + [RunPowerShellScript_RegistrationToken] + + + + + [RunPowerShellScript_GPOInstalled] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AMPs/Agent AD Status.ps1 b/AMPs/Agent AD Status.ps1 new file mode 100644 index 0000000..8ed2ab6 --- /dev/null +++ b/AMPs/Agent AD Status.ps1 @@ -0,0 +1,38 @@ +#region AMP startup variable. This region isn't in the the AMP itself. +$NetworkFolder = "Agent" +$GPOName = "n-able - Install Agent (PS)" +$NetLogonShare = "C:\Windows\SYSVOL\domain\scripts" # This is gathered inside the AMP +#endregion + +$PartnerConfigFile = $NetLogonShare + "\" + $NetworkFolder + "\PartnerConfig.xml" + +Try { + [xml]$PCXml = Get-Content -Path $PartnerConfigFile + $PartnerConfigFileVersion = $PCXml.Config.Version + $NCentralVersion = $PCXml.config.Deployment.Typical.SOAgentVersion + $InstallationFile = Try { + $NCentralFileVersion = $PCXml.config.Deployment.Typical.SOAgentFileVersion + $SOFileVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$NetLogonShare\$NetworkFolder\$($PCXml.config.Deployment.Typical.InstallFolder)\$($PCxml.Config.Deployment.Typical.SOAgentFileName)").FileVersion + if ($NCentralFileVersion -eq "$SOFileVersion.0") { "INFO: File version OK" } else { "ERROR: Wrong Installer File" } + } + Catch { + "ERROR: $($PCxml.Config.Deployment.Typical.SOAgentFileName) file not found" + } + $CustomerId = $PCXml.Config.Deployment.Typical.CustomerId + $RegistrationToken = if ($PCXml.Config.Deployment.Typical.RegistrationToken) { "INFO: Present" } else { "ERROR: Registration token missing" } +} +Catch { + $PartnerConfigFileVersion = "ERROR: PartnerConfig.xml file not found" + $NCentralVersion = "ERROR: PartnerConfig.xml file not found" + $InstallationFile = "ERROR: PartnerConfig.xml file not found" + $CustomerID = 0 + $RegistrationToken = "ERROR: PartnerConfig.xml file not found" +} + +$OSVersion = [system.environment]::OSVersion.Version +$GPOInstalled = if (($OSVersion.Major -eq 10) -or (($OSVersion.Major -eq 6) -and ($OSVersion.Minor -ge 2))) { + (get-gpo -name $GPOName -ErrorAction ignore).Count +} +else { + -1 +} \ No newline at end of file diff --git a/AMPs/Refresh Agent Token.amp b/AMPs/Refresh Agent Token.amp new file mode 100644 index 0000000..db25926 --- /dev/null +++ b/AMPs/Refresh Agent Token.amp @@ -0,0 +1,60 @@ + + + + + + + + + + 504,745 + Assembly references and imported namespaces serialized as XML namespaces + + + + + + + [username] + + + + + [password] + + + + + [serverHost] + + + + + [expirationTolerance] + + + + + [JWT] + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AMPs/Refresh Agent Token.ps1 b/AMPs/Refresh Agent Token.ps1 new file mode 100644 index 0000000..f943d10 --- /dev/null +++ b/AMPs/Refresh Agent Token.ps1 @@ -0,0 +1,73 @@ +# AMPLified version of AME-Justin's token refresh script as inspired by Chris Reid, by David Brooks +# Version 1.0.0 +# Note: Requires following NC Role permissions +# > Devices -> Network Devices -> Edit Device Settings [Read Only] +# > Devices -> Network Devices -> Registration Tokens [Manage] + +<#region Input Parameters used in AMP: + $username + $password + $serverHost + $expirationTolerance + $JWT +#> + +# Generate a pseudo-unique namespace to use with the New-WebServiceProxy and associated types. +$NWSNameSpace = "NAble" + ([guid]::NewGuid()).ToString().Substring(25) +$KeyPairType = "$NWSNameSpace.eiKeyValue" + +# Bind to the namespace, using the Webserviceproxy +$bindingURL = "https://" + $serverHost + "/dms2/services2/ServerEI2?wsdl" +$nws = New-Webserviceproxy $bindingURL -Namespace ($NWSNameSpace) + +# Set up and execute the query +$KeyPair = New-Object -TypeName $KeyPairType +$KeyPair.Key = 'listSOs' +$KeyPair.Value = "false" + +#Attempt to connect +Try { + $CustomerList = $nws.customerList("", $JWT, $KeyPair) +} +Catch { + Write-Host "Could not connect: $($_.Exception.Message)" + exit +} + +#Create customer report ArrayList +$Customers = New-Object System.Collections.ArrayList +ForEach ($Entity in $CustomerList) { + $CustomerAssetInfo = @{} + ForEach ($item in $Entity.items) { $CustomerAssetInfo[$item.key] = $item.Value } + $o = [PSCustomObject]@{ + CustomerID = $CustomerAssetInfo["customer.customerid"] + Name = $CustomerAssetInfo["customer.customername"] + ParentID = $CustomerAssetInfo["customer.parentid"] + RegistrationToken = $CustomerAssetInfo["customer.registrationtoken"] + RegistrationTokenExpiryDate = $CustomerAssetInfo["customer.registrationtokenexpirydate"] + } + $Customers.Add($o) > $null +} + +$SecurePass = ConvertTo-SecureString $password -AsPlainText -Force +$PSUserCredential = New-Object PSCredential ($username, $SecurePass) +$date = Get-Date + +function updatetoken($customer) { + $uri = "https://$serverHost/dms/FileDownload?customerID=$($customer.customerid)&softwareID=101" + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $uri -UseBasicParsing -Credential $PSUserCredential | Out-Null +} +foreach ($customer in $customers) { + if (-not ($customer.registrationtokenexpirydate)) { + #No Registration Token + updatetoken($customer) + } + #Expires soon + else { + $expirationdate = [datetime]::ParseExact(($customer.registrationtokenexpirydate).split(" ")[0], 'yyyy-MM-dd', $null) + if ($expirationdate -lt $date.AddDays($expirationTolerance)) { + updatetoken($customer) + } + } +} \ No newline at end of file diff --git a/AMPs/Update PartnerConfig from CP.amp b/AMPs/Update PartnerConfig from CP.amp new file mode 100644 index 0000000..f7f9c50 --- /dev/null +++ b/AMPs/Update PartnerConfig from CP.amp @@ -0,0 +1,91 @@ + + + + + + + + + + + 490,571 + Assembly references and imported namespaces serialized as XML namespaces + + + + + + + [LocalFolder] + + + + + [NetworkFolder] + + + + + [SOAgentFileName] + + + + + [SOAgentVersion] + + + + + [SOAgentFileVersion] + + + + + [CustomerID] + + + + + [RegistrationToken] + + + + + [Branding] + + + + + [AzNableProxyUri] + + + + + [AzNableAuthCode] + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AMPs/Update PartnerConfig from CP.ps1 b/AMPs/Update PartnerConfig from CP.ps1 new file mode 100644 index 0000000..796e28a --- /dev/null +++ b/AMPs/Update PartnerConfig from CP.ps1 @@ -0,0 +1,47 @@ +# RefreshToken + +# Get nCentral server info based on the installed agent +$AgentConfigFolder = (Get-WmiObject win32_service -filter "Name like 'Windows Agent Service'").PathName +$AgentConfigFolder = $AgentConfigFolder.Replace("bin\agent.exe", "config").Replace('"', '') +$ServerConfigXML = [xml](Get-Content "$AgentConfigFolder\ServerConfig.xml") +$serverHost = $ServerConfigXML.ServerConfig.ServerIP + +# Get $PartnerConfigFile based on the NetLogon share and the $NetworkFolder +$NetLogonShare = (get-smbshare -name NetLogon -ErrorAction SilentlyContinue).Path +# Failsafe to try it with a hardcoded version if no NetLogon share is found +If (-not $NetLogonShare) { $NetLogonShare = "C:\Windows\SYSVOL\domain\scripts" } +$PartnerConfigFile = $NetLogonShare + "\" + $NetworkFolder + "\PartnerConfig.xml" + +if (Test-Path $PartnerConfigFile) { + [xml]$xmlDocument = Get-Content -Path $PartnerConfigFile + + # Branding + $xmlDocument.Config.Branding.ErrorContactInfo = $Branding + + # Server + $xmlDocument.Config.Server.NCServerAddress = $serverHost + + ### Deployment + # LocalFolder, NetworkFolder + $xmlDocument.Config.Deployment.LocalFolder = $LocalFolder + $xmlDocument.Config.Deployment.NetworkFolder = $NetworkFolder + + # Typical + $xmlDocument.Config.Deployment.Typical.SOAgentFileName = $SOAgentFileName + $xmlDocument.Config.Deployment.Typical.SOAgentVersion = $SOAgentVersion + $xmlDocument.Config.Deployment.Typical.SOAgentFileVersion = $SOAgentFileVersion + # (Customer ID and Token) + $xmlDocument.Config.Deployment.Typical.CustomerId = $CustomerId + $xmlDocument.Config.Deployment.Typical.RegistrationToken = $RegistrationToken + + # AzNableProxy service by Kelvin Tegelaar + # Azure is the more secure way to pass the Registration token. Check Kelvin's AzNableProxy https://github.com/KelvinTegelaar/AzNableProxy + $xmlDocument.Config.Deployment.Typical.AzNableProxyUri = $AzNableProxyUri + $xmlDocument.Config.Deployment.Typical.AzNableAuthCode = $AzNableAuthCode + + $xmlDocument.Save($PartnerConfigFile) + Write-Host "Saving Config to $PartnerConfigFile" +} +else { + Write-Host "Unable to find PartnerConfig file!" +} diff --git a/AMPs/Update PartnerConfig from JWT-API.amp b/AMPs/Update PartnerConfig from JWT-API.amp new file mode 100644 index 0000000..a5792c1 --- /dev/null +++ b/AMPs/Update PartnerConfig from JWT-API.amp @@ -0,0 +1,85 @@ + + + + + + + + + + + 490,571 + Assembly references and imported namespaces serialized as XML namespaces + + + + + + + [Branding] + + + + + [LocalFolder] + + + + + [NetworkFolder] + + + + + [SOAgentFileName] + + + + + [SOAgentVersion] + + + + + [SOAgentFileVersion] + + + + + [AzNableProxyUri] + + + + + [AzNableAuthCode] + + + + + [JWT] + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AMPs/Update PartnerConfig from JWT-API.ps1 b/AMPs/Update PartnerConfig from JWT-API.ps1 new file mode 100644 index 0000000..0a921d7 --- /dev/null +++ b/AMPs/Update PartnerConfig from JWT-API.ps1 @@ -0,0 +1,167 @@ +<# +### Script of unknown origin, modified to work with 2020. +.SYNOPSIS +This script is used to update the PartnerConfig.xml used by the InstallAgent for N-Central + +.DESCRIPTION +This script is used to update the PartnerConfig.xml used by the InstallAgent for N-Central, it achieves this by: +- Pulls the Customer ID from the Agent Maintenance service, if that fails... +- - Pulls local details about the NC server and appliance ID, then retrieves the CustomerID from the NC API +- Uses the Customer ID to retrieve the Customer agent token using the NC API + +The script is intended to be run from within an AMP, and the AMP passes through the following variables associated the the following sections of the PartnerConfig.xml: +> Branding +$Branding = "My MSP @ MSP.com" + +> Deployment +$LocalFolder "" +$NetworkFolder + +> Typical +$SOAgentFileName +$SOAgentVersion +$SOAgentFileVersion + +>AzNableProxy service by Kelvin Tegelaar +>Azure is the more secure way to pass the Registration token. Check Kelvin's AzNableProxy https://github.com/KelvinTegelaar/AzNableProxy + +$AzNableProxyUri +$AzNableAuthCode + +#> + +### Begin Code + +# Get the path based on the NetLogon share +$NetLogonShare = (get-smbshare -name NetLogon -ErrorAction SilentlyContinue).Path +# Failsafe to try it with a hardcoded version if no NetLogon share is found +If (-not $NetLogonShare) { $NetLogonShare = "C:\Windows\SYSVOL\domain\scripts" } +$PartnerConfigFile = $NetLogonShare + "\" + $NetworkFolder + "\PartnerConfig.xml" + +### Method 1 of retrieving CustomerID from AgentMaintenanceSchedules.xml +### Method 1 credited to Prejay of Doherty. + +# Set CustomerId to -1, we can use this later to check if there is an issue with the provided Id or it wans't provided +$CustomerId = -1 + +$AgentMaintenanceSchedulesConfig = ("{0}\N-able Technologies\Windows Agent\config\AgentMaintenanceSchedules.xml" -f ${Env:ProgramFiles(x86)}) +if (Test-Path $AgentMaintenanceSchedulesConfig) { + $AgentMaintenanceSchedulesXML = [xml](Get-Content -Path $AgentMaintenanceSchedulesConfig) + $RebootMessagelogoURL = $AgentMaintenanceSchedulesXML.AgentMaintenanceSchedules.rebootmessagelogourl + $CustomerID = [Regex]::Matches($RebootMessagelogoURL, '(?<=\=)(.*?)(?=&)').Value +} + +# Get nCentral Server based on the installed agent +$AgentConfigFolder = (Get-WmiObject win32_service -filter "Name like 'Windows Agent Service'").PathName +$AgentConfigFolder = $AgentConfigFolder.Replace("bin\agent.exe", "config").Replace('"', '') +$ServerConfigXML = [xml](Get-Content "$AgentConfigFolder\ServerConfig.xml") +$serverHost = $ServerConfigXML.ServerConfig.ServerIP + +### Setup connection to NC Webproxy namespace, this will be used later for NC API calls +$NWSNameSpace = "NAble" + ([guid]::NewGuid()).ToString().Substring(25) +$KeyPairType = "$NWSNameSpace.EiKeyValue" + +$bindingURL = "https://" + $serverHost + "/dms2/services2/ServerEI2?wsdl" +$nws = New-Webserviceproxy $bindingURL -Namespace ($NWSNameSpace) + +### JWT +#$JWT = "Very long string" + +### Method 2 of obtaining customer ID +### Method 2 provied by Robby S, b-inside +if ($CustomerId -eq -1) { + # Determine who we are + $ApplianceConfigXML = [xml](Get-Content "$Script:AgentConfigFolder\ApplianceConfig.xml") + $applianceID = $ApplianceConfigXML.ApplianceConfig.ApplianceID + + # Setup Keypairs array and Keypair object with search params for deviceGet method used against the NC server + $KeyPairs = @() + $KeyPair = New-Object -TypeName $KeyPairType + $KeyPair.Key = 'applianceID' + $KeyPair.Value = $applianceID + $KeyPairs += $KeyPair + $rc = $nws.deviceGet("", $JWT, $KeyPairs) + Write-Host "nCentral Server = "($serverHost) + try { + $CustomerId = ($rc[0].info | ForEach-Object { if ($_.key -eq "device.customerid") { $_.Value } }) + } + catch { + Write-Host "Could not connect: $($_.Exception.Message)" + exit + } + write-host "CustomerId = $CustomerId" +} + +$badCustomerIds = @(-1, "-1", "", $null) +if ($badCustomerIds -contains $CustomerId) { + Write-Host "Unable to retrieve valid CustomerID, exiting" + Exit 2 +} + +### Now we gather the token from the N-Central server using the provided information +# Code snippet credit goes to Chris Reid, Jon Czerwinski and Kelvin Telegaar + +# Set up and execute the query +$KeyPair = New-Object -TypeName $KeyPairType +$KeyPair.Key = 'listSOs' +$KeyPair.Value = "False" +Try { + $CustomerList = $nws.customerList("", $JWT, $KeyPair) +} +Catch { + Write-Host "Could not connect: $($_.Exception.Message)" + exit +} + +$found = $False +$rowid = 0 +While ($rowid -lt $CustomerList.Count -and $found -eq $False) { + If ($customerlist[$rowid].items[0].Value -eq [int]$CustomerID) { + Foreach ($rowitem In $CustomerList[$rowid].items) { + If ($rowitem.key -eq "customer.registrationtoken") { + $RetrievedRegistrationToken = $rowitem.value + If ($RetrievedRegistrationToken -eq "") { + Write-Host "Note that a valid Registration Token was not returned even though the customer was found. This happens when an agent install has never been downloaded for that customer. Try to download an agent from the N-Central UI and run this script again" + } + } + } + } + $rowid++ +} + +Write-Host "Here is the registration token for CustomerID" $CustomerID":" $RetrievedRegistrationToken -ForegroundColor Green + +# Refresh Partner XML +if (Test-Path $PartnerConfigFile) { + [xml]$xmlDocument = Get-Content -Path $PartnerConfigFile + + # Branding + $xmlDocument.Config.Branding.ErrorContactInfo = $Branding + + # Server + $xmlDocument.Config.Server.NCServerAddress = $serverHost + + ### Deployment + # LocalFolder, NetworkFolder + $xmlDocument.Config.Deployment.LocalFolder = $LocalFolder + $xmlDocument.Config.Deployment.NetworkFolder = $NetworkFolder + + # Typical + $xmlDocument.Config.Deployment.Typical.SOAgentFileName = $SOAgentFileName + $xmlDocument.Config.Deployment.Typical.SOAgentVersion = $SOAgentVersion + $xmlDocument.Config.Deployment.Typical.SOAgentFileVersion = $SOAgentFileVersion + # (Customer ID and Token) + $xmlDocument.Config.Deployment.Typical.CustomerId = $CustomerId + $xmlDocument.Config.Deployment.Typical.RegistrationToken = $RetrievedRegistrationToken + + # AzNableProxy service by Kelvin Tegelaar + # Azure is the more secure way to pass the Registration token. Check Kelvin's AzNableProxy https://github.com/KelvinTegelaar/AzNableProxy + $xmlDocument.Config.Deployment.Typical.AzNableProxyUri = $AzNableProxyUri + $xmlDocument.Config.Deployment.Typical.AzNableAuthCode = $AzNableAuthCode + + $xmlDocument.Save($PartnerConfigFile) + Write-Host "Saving Config to $PartnerConfigFile" +} +else { + Write-Host "Unable to find PartnerConfig file!" +} diff --git a/Agent/AgentCleanup4.exe b/Agent/AgentCleanup4.exe new file mode 100644 index 0000000..ffd6fe1 Binary files /dev/null and b/Agent/AgentCleanup4.exe differ diff --git a/Deployment Package/AGENT/CurrentAgent/NET 4.5.2 Installer Download.url b/Agent/CurrentAgent/NET 4.5.2 Installer Download.url similarity index 97% rename from Deployment Package/AGENT/CurrentAgent/NET 4.5.2 Installer Download.url rename to Agent/CurrentAgent/NET 4.5.2 Installer Download.url index 6893964..605875a 100644 --- a/Deployment Package/AGENT/CurrentAgent/NET 4.5.2 Installer Download.url +++ b/Agent/CurrentAgent/NET 4.5.2 Installer Download.url @@ -1,5 +1,5 @@ -[{000214A0-0000-0000-C000-000000000046}] -Prop3=19,11 -[InternetShortcut] -IDList= -URL=https://download.microsoft.com/download/E/2/1/E21644B5-2DF2-47C2-91BD-63C560427900/NDP452-KB2901907-x86-x64-AllOS-ENU.exe +[{000214A0-0000-0000-C000-000000000046}] +Prop3=19,11 +[InternetShortcut] +IDList= +URL=https://download.microsoft.com/download/E/2/1/E21644B5-2DF2-47C2-91BD-63C560427900/NDP452-KB2901907-x86-x64-AllOS-ENU.exe diff --git a/Deployment Package/AGENT/CurrentAgent/NET4_5_2-Universal.exe.txt b/Agent/CurrentAgent/NET4_5_2-Universal.exe.txt similarity index 99% rename from Deployment Package/AGENT/CurrentAgent/NET4_5_2-Universal.exe.txt rename to Agent/CurrentAgent/NET4_5_2-Universal.exe.txt index 0e3ca55..bc7d8db 100644 --- a/Deployment Package/AGENT/CurrentAgent/NET4_5_2-Universal.exe.txt +++ b/Agent/CurrentAgent/NET4_5_2-Universal.exe.txt @@ -1,3 +1,3 @@ -- Download the .NET Framework 4.5.2 Universal Installer from Microsoft by using the provided URL File in this Folder -- Place it here, and call it by the same name of this text file (without the txt, of course!) +- Download the .NET Framework 4.5.2 Universal Installer from Microsoft by using the provided URL File in this Folder +- Place it here, and call it by the same name of this text file (without the txt, of course!) - Or, optionally, rename it and update the Property in PartnerConfig.xml! \ No newline at end of file diff --git a/Deployment Package/AGENT/CurrentAgent/WindowsAgentSetup.exe.txt b/Agent/CurrentAgent/WindowsAgentSetup.exe.txt similarity index 99% rename from Deployment Package/AGENT/CurrentAgent/WindowsAgentSetup.exe.txt rename to Agent/CurrentAgent/WindowsAgentSetup.exe.txt index 55db885..87b56e0 100644 --- a/Deployment Package/AGENT/CurrentAgent/WindowsAgentSetup.exe.txt +++ b/Agent/CurrentAgent/WindowsAgentSetup.exe.txt @@ -1,3 +1,3 @@ -- Download the desired Version of your SYSTEM-LEVEL N-Central Agent -- Place it here, and call it by the same name of this text file (without the txt, of course!) +- Download the desired Version of your SYSTEM-LEVEL N-Central Agent +- Place it here, and call it by the same name of this text file (without the txt, of course!) - Or, optionally, rename it and update the in PartnerConfig.xml! \ No newline at end of file diff --git a/Agent/InstallAgent.ps1 b/Agent/InstallAgent.ps1 new file mode 100644 index 0000000..a54afba --- /dev/null +++ b/Agent/InstallAgent.ps1 @@ -0,0 +1,835 @@ +# Installation, Diagnostic and Repair Script for the N-Central Agent +# Original Script Created by Tim Wiser +# Maintained by the N-able Community + +################################ +########## Change Log ########## +################################ + +### 6.0.1 on 2021-04-12 - David Brooks & Robby Swartenbroekx +################################################################## +# See ReleaseNotes.md for all the changes + +### 6.0.0 on 2021-02-01 - David Brooks +################################################################## +# - Added various token registration method and agent activation methods +# - -Activation Key : Token (Partner Config) / Appliance ID (Existing Installation) +# - -Activation Key : Token (Current Script) / Appliance ID (Existing Installation) +# - -Activation Key : Token / Appliance ID (Historical Installation) +# - -Activation Key : AzNableProxy Token / Customer ID, Appliance ID (Existing Installation) +# - -Activation Key : Customer ID (Current Script) / AzNableProxy Token / Appliance ID (Existing Installation) +# - -Site ID/Registration Token (Current Script) +# - -Site ID/Registration Token (Historical Installation) +# - -Customer ID / Registration Token (Partner Config) +# - -Customer ID / AzNableProxy Token (Partner Config) +# - -Customer ID / AzNableProxy Token (Current Script) +# - Removed Legacy agent installation method as it is no longer possible due to token registration + +### 5.0.1 on 2019-08-26 - Ryan Crowther Jr +################################################################## +# FIXES/FEATURES +# - Added Detection Support for .NET Framework 4.8 +# - Fixed an issue where a newer Agent Version was not Detected because of the bizarre Windows +# Versioning method for the Agent Installer, for example Version 12.1.2008.0 (12.1 HF1) is +# "greater than" Version 12.1.10241.0 (12.1 SP1 HF1) +# - Added Script Instance Awareness +# - The Script will first check to see if another Instance is already in progress, and if so, +# terminate the Script with an Event Log entry indicating this, in order to preserve Registry +# results of the pre-existing Instance +# - If the pre-existing Instance has been active for more than 30 minutes, the Script will +# proceed anyway, thus overwriting results +# - Removed references to the PS 3.0 function Get-CIMInstance (from a previous optimization) to +# maintain PS 2.0 Compatibility +# - Fixed a premature stop error in the Launcher when a Device has .NET 2.0 SP1 installed, but +# needs to install PowerShell 2.0 (Thank you, Harvey!) +# - Fixed an issue during Diagnosis phase where incorrect Service Startup Behavior was ALWAYS +# detected, even after Repairs complete successfully + +### 5.0.0 on 2018-11-08 - Ryan Crowther Jr +################################################################## +# OPTIMIZATION +# - Converted InstallAgent.vbs to PowerShell 2.0 Compatible Script (InstallAgent.ps1) (SEE NOTES +# SECTION BELOW) +# - Converted InstallAgent.ini to XML file (PartnerConfig.xml) for direct parsing and variable +# typing in PowerShell 2.0 +# FIXES/FEATURES +# - Reworked Windows Event Log Reporting +# 1 - Detailed Script Results are packaged in a single Event +# 2 - Missing/Invalid Items required by the Script are identified along with provided resolutions +# 3 - Problems discovered with the Agent are listed in addition to Repair actions taken and their +# results +# 4 - Details regarding Installers used and Install Methods attempted +# - Reworked Windows Registry Reporting +# 1 - Prerequisite Launcher now stores Execution values in the same root key as the Setup Script +# 2 - Action and Sequence Updates are made to the Registry in real time as the Script progresses +# - Added Local Agent Activation Info retention (Location specified in Partner Configuration) +# - Added Activation Key Builder (based on Appliance Info) +# - Script prioritizes Activation Info as follows: +# 1 - Discovered Activation Key (currently installed Agent) +# 2 - Discovered Customer/Site ID (currently installed Agent) +# 3 - Historical Activation Key (Local History File) +# 4 - Historical Customer/Site ID (Local History File) +# 5 - Default Customer ID for New Devices (GPO/Command-Line Parameter) +# 6 - Historical Default Customer ID (Local History File, if no GPO/Command-Line Parameter is +# Present/Valid) +# - Added Repair for Invalid Appliance ID in ApplianceConfig.xml, typically -1, which causes the +# N-Central Server to be unable to map the Agent to a Device (results in an Agent that either +# never Imports or spontaneously dies) +# - Added Legacy Agent parameters to Partner Configuration to support installation/repair of an +# older Agent on Windows XP/Server 2003 (provided Agent copy is 11.0.0.1114 aka 11.0 HF3) +# - Added Takeover Action for Rogue/Competitor-Controlled Agents (if the configured Server in the +# current installation does not match the N-Central Server Address in the Partner Configuration, +# the Agent will be reinstalled using the Script Customer ID - this also fixes an Agent +# configured with "localhost" N-Central Server Address) +# HOUSEKEEPING +# - Re-published Change Log with most recent developments up top and some basic Categories for +# updates +# - Moved Script Execution Registry Key to HKLM:\SOFTWARE\SolarWinds MSP Community +# - Added a Legacy Version Cleanup section which will automatically remove values/files created by +# older versions of the Script (Huge thanks to Tim and Jon for their contributions!) +# +### NOTES ON POWERSHELL 2.0 CONVERSION IN 5.0.0 +################################################################## +# The intent of the conversion is to: +# 1 - Maintain currency of the Scripting Platform and key features +# 2 - Remove the need to pass Configuration variables between Scripts +# 3 - Remove lesser-used/deprecated features of the original VBScript +# 4 - Categorize Script actions by Sequence for better reporting clarity +# 5 - Simplify and organize Script body by making use of PowerShell Modules + +### 4.26 on 2018-10-17 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Fixed strScriptPath bad declaration + +### 4.25 on 2018-01-28 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Detect whether .ini file is saved with ASCII encoding (Log error and exit if not) + +### 4.24 on 2017-10-16 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Rebased on .NET 4.5.2 +# - Reorganized prerequisite checks + +### 4.23 on 2017-10-02 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Bug fix on checking executable path (Special Thanks to Rod Clark!) + +### 4.22 on 2017-06-21 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Close case where service is registered but executable is missing + +### 4.21 on 2017-01-26 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Error checking for missing or empty configuration file + +### 4.20 on 2017-01-19 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Moved partner-configured parameters out to AgentInstall.ini +# - Removed Windows 2000 checks +# - Cleaned up agent checks to eliminate redundant calls to StripAgent +# - Remove STARTUP|SHUTDOWN mode + +### 4.10 on 2015-11-15 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Aligned XP < SP3 exit code with documentation (was 3, should be 1) +# - Added localhost zombie checking +# HOUSEKEEPING +# - Changed registry location to HKLM:\Software\N-Central +# OPTIMIZATION +# - Refactored code (SEE NOTES SECTION BELOW) +# - Moved mainline code to subroutines, replaced literals with CONSTs +# +### NOTES ON REFACTORING IN 4.10 +################################################################## +# The intent of the refactoring is: +# 1 - Shorten and simplify the mainline of code by moving larger sections of mainline code to +# subroutines +# 2 - Replace areas where the code quit from subroutines and functions with updates to runState +# variable and flow control in the mainline. The script will quit the mainline with its final +# runState. +# 3 - Remove the duplication of code +# 4 - Remove inaccessible code + +### 4.01 on 2015-11-09 - Jon Czerwinski +################################################################## +# FIXES/FEATURES +# - Corrected agent version zombie check + +### 4.00 on 2015-02-11 - Tim Wiser +################################################################## +# FIXES/FEATURES +# - Formatting changes and more friendly startup message +# - Dirty exit now shows error message and contact information on console +# - Added 'Checking files' bit to remove confusing delay at that stage (No spinner though, +# unfortunately) +# HOUSEKEEPING +# - Final Release by Tim Wiser :o( +# - Committed to github by Jon Czerwinski + +########################################## +########## Constant Definitions ########## +########################################## + +### Command-Line/Group Policy Parameters +################################################################## +param ( + [Parameter(Mandatory = $true)] + $LauncherPath, + [Parameter(Mandatory = $false)] + $CustomerID, + [Parameter(Mandatory = $false)] + $RegistrationToken, + [Parameter(Mandatory = $false)] + [Switch]$DebugMode +) + +if ($DebugMode.IsPresent) { + if (Get-Module InstallAgent-Core) { + Remove-Module InstallAgent-Core + } + $AgentRegPath = "HKLM:\SOFTWARE\N-able Community\InstallAgent" + if (Test-Path $AgentRegPath) { + Remove-Item $AgentRegPath -Recurse -Force + } +} + +### N-Central Constants +################################################################## +### Current Values +# Execution Constants +$NC = @{} +# Install Constants +$NC.InstallParameters = @{ + "A" = "AGENTACTIVATIONKEY" + "B" = "CUSTOMERID" + "C" = "CUSTOMERSPECIFIC" + "D" = "SERVERADDRESS" + "E" = "SERVERPORT" + "F" = "SERVERPROTOCOL" + "G" = "AGENTPROXY" + "H" = "REGISTRATION_TOKEN" +} +# Path Constants +$NC.Paths = @{ + "BinFolder" = "N-able Technologies\Windows Agent\bin" + "ConfigFolder" = "N-able Technologies\Windows Agent\config" + "UninstallKey32" = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" + "UninstallKey64" = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" +} +# Product Constants +$NC.Products = @{ + "Agent" = @{ + "ApplianceConfig" = "ApplianceConfig.xml" + "ApplianceConfigBackup" = "ApplianceConfig.xml.backup" + "IDName" = "N-Central Customer ID" + "InstallLog" = "Checker.log" + "InstallLogFields" = @( + "Activation Key", + "Appliance ID", + "Customer ID", + "Install Time", + "Package Version", + "Server Endpoint" + ) + "InstallerName" = "Windows Agent Installer" + "MaintenanceProcess" = "AgentMaint.exe" + "MaintenanceService" = "Windows Agent Maintenance Service" + "Name" = "N-Central Agent" + "Process" = "agent.exe" + "ServerConfig" = "ServerConfig.xml" + "ServerConfigBackup" = "ServerConfig.xml.backup" + "ServerDefaultValue" = "localhost" + "Service" = "Windows Agent Service" + "WindowsName" = "Windows Agent" + } + "NCServer" = @{ + "Name" = "Partner N-Central Server" + } +} +# Validation Constants +$NC.Validation = @{ + "ActivationKey" = @{ "Encoded" = '^[a-zA-Z0-9+/]{25,}={0,2}$' } + "ApplianceID" = '^[0-9]{5,}$' + "CustomerID" = '^[0-9]{3,4}$' + "ServerAddress" = @{ + "Accepted" = '^[a-zA-Z]{3,}://[a-zA-Z_0-9\.\-]+$' + "Valid" = '^[a-zA-Z_0-9\.\-]+$' + } + "CustomerIDandToken" = '^[0-9]{3,4}[|](?im)[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$' +} + +### Script Constants +################################################################## +### Current Values +# Execution Constants +$SC = @{ + "DateFormat" = @{ + "FullMessageOnly" = "%Y-%m-%d at %r" + "Full" = "%Y-%m-%d %r" + "Short" = "%Y-%m-%d" + } + "ExecutionMode" = @{ + "A" = "On-Demand" + "B" = "Group Policy" + } + "ErrorScriptResult" = "Script Terminated Unexpectedly" + "InitialScriptAction" = "Importing Function Library" + "InitialScriptResult" = "Script In Progress" + "InstallKit" = @{ + "A" = "None Eligible" + "B" = "Group Policy" + "C" = "On-Demand" + } + "RunningInstanceTimeout" = 30 + "ScriptEventLog" = "Application" + "ScriptVersion" = "6.0.1" + "SuccessScriptAction" = "Graceful Exit" + "SuccessScriptResult" = "Script Completed Successfully" +} +# Appliance Status Constants +$SC.ApplianceStatus = @{ + "A" = "Optimal" + "B" = "Marginal" + "C" = "Orphaned" + "D" = "Disabled" + "E" = "Rogue / Competitor-Controlled" + "F" = "Corrupt" + "G" = "Missing" +} +# Exit Code Constants +$SC.ExitTypes = @{ + "A" = "Successful" + "B" = "Check Configuration" + "C" = "Server Unavailable" + "D" = "Unsuccessful" + "E" = "Report This Error" +} +$SC.Exit = @{ + "Error" = @{ + "ExitResult" = "Undocumented Error (See Event Log)" + "ExitType" = $SC.ExitTypes.E + } + "A" = @{ + "ExitResult" = $SC.SuccessScriptResult + "ExitType" = $SC.ExitTypes.A + } + "B" = @{ + "ExitResult" = "Partner Configuration File is Missing" + "ExitType" = $SC.ExitTypes.B + } + "C" = @{ + "ExitResult" = "Partner Configuration is Invalid" + "ExitType" = $SC.ExitTypes.B + } + "D" = @{ + "ExitResult" = "No Installation Sources Available" + "ExitType" = $SC.ExitTypes.B + } + "E" = @{ + "ExitResult" = "Installer File is Missing" + "ExitType" = $SC.ExitTypes.B + } + "F" = @{ + "ExitResult" = "Installer Version Mismatch" + "ExitType" = $SC.ExitTypes.B + } + "G" = @{ + "ExitResult" = ("Unable to Reach " + $NC.Products.NCServer.Name) + "ExitType" = $SC.ExitTypes.C + } + "H" = @{ + "ExitResult" = "Customer ID Parameter Required" + "ExitType" = $SC.ExitTypes.B + } + "I" = @{ + "ExitResult" = "Customer ID Parameter Invalid" + "ExitType" = $SC.ExitTypes.B + } + "J" = @{ + "ExitResult" = "Windows Installer Service Unavailable" + "ExitType" = $SC.ExitTypes.D + } + "K" = @{ + "ExitResult" = ".NET Framework Installation Failed" + "ExitType" = $SC.ExitTypes.D + } + "L" = @{ + "ExitResult" = "Agent Removal Failed" + "ExitType" = $SC.ExitTypes.D + } + "M" = @{ + "ExitResult" = "No Installation Methods Remaining" + "ExitType" = $SC.ExitTypes.D + } + "AA" = @{ + "ExitMessage" = "An invalid Parameter value or type was provided to a Script Function." + "ExitResult" = "Invalid Parameter" + "ExitType" = $SC.ExitTypes.E + } + "AB" = @{ + "ExitMessage" = ("The current " + $NC.Products.Agent.Name + " installation requires repair, but no Repairs were selected to be applied.") + "ExitResult" = "No Repairs Selected" + "ExitType" = $SC.ExitTypes.E + } + "AC" = @{ + "ExitMessage" = "An error occurred during a file transfer and the Script cannot proceed." + "ExitResult" = "File Transfer Failed" + "ExitType" = $SC.ExitTypes.E + } + "AD" = @{ + "ExitMessage" = "The file at the specified path does not exist." + "ExitResult" = "File Not Found" + "ExitType" = $SC.ExitTypes.E + } + "AE" = @{ + "ExitMessage" = "An error occurred during item creation and the Script cannot proceed." + "ExitResult" = "File/Folder Creation Failed" + "ExitType" = $SC.ExitTypes.E + } + "AF" = @{ + "ExitMessage" = "The agent could not be installed on this legacy platform." + "ExitResult" = "Legacy installation unavailable" + "ExitType" = $SC.ExitTypes.E + } +} +# Install Constants +$SC.InstallActions = @{ + "A" = "Install New" + "B" = "Upgrade Existing" + "C" = "Replace Existing" +} +$SC.InstallMethods = @{ + "Attempts" = @{ + "A" = 1 + "B" = 1 + "C" = 1 + "D" = 1 + "E" = 1 + "F" = 1 + "G" = 1 + "H" = 1 + } + "Names" = @{ + #String validated 7/02/2021 + #Activation Key: Appliance Server/Appliance App ID/Script Token + "A" = "Activation Key : Token (Current Script) / Appliance ID (Existing Installation)" + #String validated 7/02/2021 + #Activation Key: Appliance Server/Appliance App ID/Config Token + "B" = "Activation Key : Token (Partner Config) / Appliance ID (Existing Installation)" + #String validated 6/02/2021 - needs further review, activation key pulled from method UpdateHistory + "C" = "Activation Key : Token / Appliance ID (Historical Installation)" + #String validated 7/02/2021 + "D" = "Activation Key : AzNableProxy Token / Customer ID, Appliance ID (Existing Installation)" + #String validated 7/02/2021 + "E" = "Activation Key : Customer ID (Current Script) / AzNableProxy Token / Appliance ID (Existing Installation)" + #String validated 7/02/2021 + "F" = "Site ID/Registration Token (Current Script)" + #TBD - May remove + "G" = "Site ID/Registration Token (Historical Installation)" + #String validated 7/02/2021 + "H" = "Customer ID / Registration Token (Partner Config)" + #String validated 7/02/2021 + "I" = "Customer ID (Current Script) / AzNableProxy Token " + #String validated 7/02/2021 + "J" = "Customer ID (Partner Config) / AzNableProxy Token " + + } + "Type" = @{ + "A" = "Activation Key: Token/AppId" + "B" = "Activation Key: Token/AppId" + "C" = "Activation Key: Token/AppId" + "D" = "Activation Key: AzNableProxy->Token/AppId" + "E" = "Activation Key: AzNableProxy->Token/AppId" + "F" = "Registration Token: CustomerId/Token" + "G" = "Registration Token: CustomerId/Token" + "H" = "Registration Token: CustomerId/Token" + "I" = "Registration Token: CustomerId/AzNableProxy->Token" + "J" = "Registration Token: CustomerId/AzNableProxy->Token" + } + "InstallTypes" = @{ + "A" = "Activation Key: Token/AppId" + "B" = "Activation Key: AzNableProxy->Token/AppId" + "C" = "Registration Token: CustomerId/Token" + "D" = "Registration Token: CustomerId/AzNableProxy->Token" + } + "UsesAzProxy" = @{ + "A" = $false + "B" = $false + "C" = $false + "D" = $true + "E" = $true + "F" = $false + "G" = $false + "H" = $false + "I" = $true + "J" = $true + } +} +# Name Constants +$SC.Names = @{ + "HistoryFile" = "AgentHistory.xml" + "Launcher" = "LaunchInstaller" + "LauncherFile" = "LaunchInstaller.bat" + "LauncherProduct" = "Agent Setup Launcher" + "LibraryFiles" = @("InstallAgent-Core.psm1") + "PartnerConfig" = "PartnerConfig.xml" + "Script" = "InstallAgent" + "ScriptProduct" = "Agent Setup Script" +} +# Path Constants +$SC.Paths = @{ + "ExecutionKey" = "HKLM:\SOFTWARE\N-able Community" + "ServiceKey" = "HKLM:\SYSTEM\CurrentControlSet\Services" + "TempFolder" = Split-Path $MyInvocation.MyCommand.Path -Parent +} +$SC.Paths.EventServiceKey = @($SC.Paths.ServiceKey, "EventLog") -join '\' +# Repair Constants +$SC.RepairActions = @{ + "A" = "Install" + "B" = "RestartServices" +} +$SC.Repairs = @{ + "PostRepair" = @{ + "Name" = "Post-Repair Actions" + } + "Recovery" = @{ + "Name" = "Recovery Actions" + } + "A" = @{ + "Name" = "Fix - Orphaned Appliance" + "PostRepairAction" = $SC.RepairActions.B + "RecoveryAction" = $null + } + "B" = @{ + "Name" = "Fix - Incorrect Service Startup Type" + "PostRepairAction" = $null + "RecoveryAction" = $SC.RepairActions.A + } + "C" = @{ + "Name" = "Fix - Incorrect Service Behavior" + "PostRepairAction" = $null + "RecoveryAction" = $SC.RepairActions.A + } + "D" = @{ + "Name" = "Fix - Process/Service Not Running" + "PostRepairAction" = $null + "RecoveryAction" = $SC.RepairActions.A + } +} +# Sequence Constants +$SC.SequenceMessages = @{ + "A" = $null + "B" = "Validating execution requirements..." + "C" = "Diagnosing existing Agent Installation..." + "D" = "Selecting and performing applicable repairs..." + "E" = "Checking installation requirements..." +} +$SC.SequenceNames = @{ + "A" = "Launcher" + "B" = "Validation" + "C" = "Diagnosis" + "D" = "Repair" + "E" = "Installation" +} +$SC.SequenceStatus = @{ + "A" = "COMPLETE" + "B" = "EXITED" + "C" = "IN PROGRESS" + "D" = "SKIPPED" + "E" = "ABORTED" + "F" = "FAILED" +} +# Validation Constants +$SC.Validation = @{ + "Docs" = @{ + $NC.Products.Agent.ApplianceConfig = "Appliance" + $NC.Products.Agent.InstallLog = "Appliance" + $NC.Products.Agent.ServerConfig = "Appliance" + $SC.Names.HistoryFile = "History" + "Registry" = "Registry" + } + "FileNameEXE" = '^((?![<>:"/\\|?*]).)+\.[Ee][Xx][Ee]$' + "FileNameXML" = '^((?![<>:"/\\|?*]).)+\.[Xx][Mm][Ll]$' + "InternalErrorCode" = '^1[0-9]{2}$' + "ItemName" = '^((?![<>:"/\\|?*]).)+$' + "GUID" = '(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$' + "LocalFilePathXML" = '^[a-zA-Z]:\\([^ <>:"/\\|?*]((?![<>:"/\\|?*]).)+((?:"/\\|?*]((?![<>:"/\\|?*]).)+((?:"/\\|?*]((?![<>:"/\\|?*]).)+((?$null | + Select-Object -ExpandProperty ScriptResult 2>$null + ) -eq $SC.InitialScriptResult + ) -and + ( + ( + Get-Date ( + Get-ItemProperty $Script.Results.ScriptKey 2>$null | + Select-Object -ExpandProperty ScriptLastRan 2>$null + ) + ) -gt + (Get-Date).AddMinutes( - ($SC.RunningInstanceTimeout)) + ) +) { + # Another Script is in Progress + # Create a New Key for the Event Source if Required + if ((Test-Path $Script.Results.ScriptEventKey) -eq $false) + { New-EventLog -Source $Script.Results.ScriptSource -LogName $Script.Results.EventLog } + # Write the Event + $Message = ( + "Another Instance of the " + $SC.Names.ScriptProduct + " is currently in progress. " + + "Please review the status of the current Instance by opening the Registry to [" + + $Script.Results.ScriptKey + "].`n" + ) + Write-EventLog -LogName $Script.Results.EventLog -Source $Script.Results.ScriptSource -EventID 9999 -EntryType "Error" -Message $Message -Category 0 + # Cleanup Working Folder + Remove-Item c -Force -Recurse 2>$null + exit +} +### Write Registry Values for Script Startup +# Create Script Execution Key if Required +if ((Test-Path $Script.Results.ScriptKey) -eq $false) +{ New-Item $Script.Results.ScriptKey -Force >$null } +else { + # Remove Sequence Data from Previous Run + Get-ChildItem $Script.Results.ScriptKey | Remove-Item -Force + # Remove Transient Properties from Previous Run + Get-ItemProperty $Script.Results.ScriptKey 2>$null | + Get-Member -MemberType NoteProperty | + Where-Object { $_.Name -match '^Script' } | + ForEach-Object { Remove-ItemProperty $Script.Results.ScriptKey -Name $_.Name -Force } +} +# Update Execution Properties +$Script.Execution.Keys | +ForEach-Object { New-ItemProperty -Path $Script.Results.ScriptKey -Name $_ -Value $Script.Execution.$_ -Force >$null } +### Import Library Items +$SC.Names.LibraryFiles | +ForEach-Object { + $ModuleName = $_ + try + { Import-Module $(@($Script.Path.Library, $ModuleName) -join '\') -ErrorAction Stop } + catch { + # Get the Exception Info + $ExceptionInfo = $_.Exception + # Create a New Key for the Event Source if Required + $Script.Execution.LastResult = $SC.ErrorScriptResult + if ((Test-Path $Script.Results.ScriptEventKey) -eq $false) + { New-EventLog -Source $Script.Results.ScriptSource -LogName $Script.Results.EventLog } + # Write the Event + $Message = ( + "The Function Library for the " + $SC.Names.ScriptProduct + " is either missing or corrupt. " + + "Please verify " + $ModuleName + " exists in the [" + $Script.Path.Library + "] folder, or restore the file to its original state.`n" + ) + Write-EventLog -LogName $Script.Results.EventLog -Source $Script.Results.ScriptSource -EventID 9999 -EntryType "Error" -Message $Message -Category 0 + # Update Execution Properties + $Script.Execution.Keys | + ForEach-Object { New-ItemProperty -Path $Script.Results.ScriptKey -Name $_ -Value $Script.Execution.$_ -Force >$null } + # Cleanup Working Folder + Remove-Item $Script.Path.TempFolder -Force -Recurse 2>$null + exit + } +} +### Import Partner Configuration +try +{ [Xml] $Partner = Get-Content $Script.Path.PartnerFile 2>&1 -ErrorAction Stop } +catch { + $ExceptionInfo = $_.Exception + $InvocationInfo = $_.InvocationInfo + CatchError 1 ("Unable to read Partner Configuration at [" + $Script.Path.PartnerFile + "]") -Exit +} +### Get Local Device Info +GetDeviceInfo +### Populate Agent Log Paths using Discovered Device Info +$Agent.Path = @{ + "Checker" = @($Device.PF32, $NC.Paths.BinFolder, $NC.Products.Agent.InstallLog) -join '\' + "ApplianceConfig" = @($Device.PF32, $NC.Paths.ConfigFolder, $NC.Products.Agent.ApplianceConfig) -join '\' + "ApplianceConfigBackup" = @($Device.PF32, $NC.Paths.ConfigFolder, $NC.Products.Agent.ApplianceConfigBackup) -join '\' + "ServerConfig" = @($Device.PF32, $NC.Paths.ConfigFolder, $NC.Products.Agent.ServerConfig) -join '\' + "ServerConfigBackup" = @($Device.PF32, $NC.Paths.ConfigFolder, $NC.Products.Agent.ServerConfigBackup) -join '\' +} +### Elevate Privilege and Re-Run if Required +SelfElevate + +######################################### +########## Main Execution Body ########## +######################################### + +try { + ### VALIDATION SEQUENCE + ################################################################## + Log -BeginSequence -Message $SC.SequenceMessages.B -Sequence $SC.SequenceNames.B + # Validate Partner Configuration Values + ValidatePartnerConfig + # Validate Execution Mode + ValidateExecution + Log -EndSequence + ################################################################## + ### + + ### DIAGNOSIS SEQUENCE + ################################################################## + Log -BeginSequence -Message $SC.SequenceMessages.C -Sequence $SC.SequenceNames.C + # Diagnose Current Installation Status + DiagnoseAgent + Log -EndSequence + ################################################################## + ### + + ### REPAIR SEQUENCE + ################################################################## + Log -BeginSequence -Message $SC.SequenceMessages.D -Sequence $SC.SequenceNames.D + # Repair the Current Installation + RepairAgent + Log -EndSequence + ################################################################## + ### + + ### INSTALL SEQUENCE + ################################################################## + Log -BeginSequence -Message $SC.SequenceMessages.E -Sequence $SC.SequenceNames.E + # Verify Install Prerequisites + VerifyPrerequisites + # Replace/Upgrade or Install a New Agent + InstallAgent + Log -EndSequence + ################################################################## + ### +} +catch { + # Terminate Abnormally (Undocumented Error Occurred) + $ExceptionInfo = $_.Exception + $InvocationInfo = $_.InvocationInfo + CatchError -Exit +} + +# Terminate Successfully +Quit 0 \ No newline at end of file diff --git a/Agent/LaunchInstaller.bat b/Agent/LaunchInstaller.bat new file mode 100644 index 0000000..a17c357 --- /dev/null +++ b/Agent/LaunchInstaller.bat @@ -0,0 +1,192 @@ +@ECHO OFF +SETLOCAL EnableDelayedExpansion +SET NL=^ + + +REM = ### ABOUT +REM - Agent Setup Launcher +REM Updated by David B, PTS. Robby S, b-inside. 2021-02-21 +REM by Ryan Crowther Jr, RADCOMP Technologies - 2019-08-26 +REM - Original Script (InstallAgent.vbs) by Tim Wiser, GCI Managed IT - 2015-03 + +REM = ### USAGE +REM - This Launcher should ideally be called by a Group Policy with the +REM client's Customer-Level N-Central ID as the only Parameter, but may +REM also be run On-Demand from another local or network location, using +REM the same argument. See the README.md for detailed Deployment Steps. + +REM = ### KNOWN ISSUES +REM - WIC Installation Method not yet implemented, this affects: +REM -- Confirmed - Windows XP 64-bit (WIC must be installed manually after Service Pack 2 is installed) +REM -- Untested - Windows Server 2003 (same behavior expected, since XP-64 was based on this Build) + +REM = ### USER DEFINITIONS - Feel free to change these +REM - Working Folder +SET TempFolder=C:\Windows\Temp\AGPO +REM - Maximum Download Attempts (per File) +SET DLThreshold=3 +REM - Don't use the arguments. This way, CustomerID and Registration Token aren't taken from the arguments. This to support older GPO's, that had CustomerID and Domainname as arguments +SET NoArgs=0 +REM = ### DEFINITIONS +@ECHO OFF +set ArgCount=0 +IF %NoArgs% EQU 0 ( + FOR %%x in (%*) do ( + set /A ArgCount+=1 + ) +) +ECHO Number of arguments: %ArgCount% +REM - Launcher Script Name +SET "LauncherScript=Agent Setup Launcher" +ECHO %LauncherScript% +REM - Setup Script Name +SET "SetupScript=Agent Setup Script" +REM - Default Customer ID +SET CustomerID=%1% +REM - Activation token +SET RegistrationToken=%2% +REM - Working Library Folder +SET LibFolder=%TempFolder%\Lib +REM - Deployment Folder +SET DeployFolder=%~dp0 +echo %DeployFolder% +SET DeployLib=%DeployFolder%Lib +REM - OS Display Name +FOR /F "DELIMS=|" %%A IN ('WMIC OS GET NAME ^| FIND "Windows"') DO SET OSCaption=%%A +ECHO "%OSCaption%" | FIND "Server" >NUL +REM - Server OS Type - Next line is Legacy +REM IF %ERRORLEVEL% EQU 0 (SET OSType=Server) +REM - OS Build Number +FOR /F "TOKENS=2 DELIMS=[]" %%A IN ('VER') DO SET OSBuild=%%A +SET OSBuild=%OSBuild:~8% +REM - OS Architecture - Next 2 lines are obsolete +REM ECHO %PROCESSOR_ARCHITECTURE% | FIND "64" >NUL +REM IF %ERRORLEVEL% EQU 0 (SET OSArch=x64) ELSE (SET OSArch=x86) +REM - Program Files Folder - Next 2 lines are obsolete +REM IF "%OSArch%" EQU "x64" (SET "PF32=%SYSTEMDRIVE%\Program Files (x86)") +REM IF "%OSArch%" EQU "x86" (SET "PF32=%SYSTEMDRIVE%\Program Files") + +REM = ### BODY +ECHO == Launcher Started == + +:CheckOSRequirements +REM = Check for OS that may Require PowerShell 2.0 Installation +REM - Windows 10 +IF "%OSBuild:~0,3%" EQU "10." ( + IF %OSBuild:~3,1% EQU 0 (GOTO LaunchScript) +) +REM - Windows 7/8/8.1 and Server 2008 R2/2012/2012 R2 +IF "%OSBuild:~0,2%" EQU "6." ( + IF %OSBuild:~2,1% GTR 0 (GOTO LaunchScript) +) +REM - Windows Vista and Server 2008 +SET LegacyWait=0 +IF "%OSBuild:~0,3%" EQU "6.0" (SET LegacyWait=1) +REM - Windows XP x64 and Server 2003 +IF "%OSBuild:~0,3%" EQU "5.2" (GOTO QuitIncompatible) +REM - Windows XP +IF "%OSBuild:~0,3%" EQU "5.1" (GOTO QuitIncompatible) +REM - Older Versions (NT and Below) +IF "%OSBuild:~0,3%" EQU "5.0" (GOTO QuitIncompatible) +IF %OSBuild:~0,1% LSS 5 (GOTO QuitIncompatible) + +:CheckPSVersion +REM - Verify PowerShell Installation +REG QUERY "HKLM\SOFTWARE\Microsoft\PowerShell\1" /v Install 2>NUL | FIND "Install" >NUL +IF %ERRORLEVEL% EQU 0 ( + FOR /F "TOKENS=3" %%A IN ('REG QUERY "HKLM\SOFTWARE\Microsoft\PowerShell\1" /v Install ^| FIND "Install"') DO SET PSInstalled=%%A +) +IF "%PSInstalled%" EQU "0x1" ( + REM - Get PowerShell Version + FOR /F "TOKENS=3" %%A IN ('REG QUERY "HKLM\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine" /v PowerShellVersion ^| FIND "PowerShellVersion"') DO SET PSVersion=%%A +) +IF "%PSVersion%" EQU "2.0" (GOTO LaunchScript) + +:LaunchScript +REM - Create Local Working Folder +IF EXIST "%TempFolder%\*" (SET PathType=Directory) +IF EXIST "%TempFolder%" ( + IF "%PathType%" NEQ "Directory" ( +REM DEL /Q "%TempFolder%" + MKDIR "%TempFolder%" + ) +) ELSE (MKDIR "%TempFolder%") +SET PathType= +IF EXIST "%LibFolder%\*" (SET PathType=Directory) +IF EXIST "%LibFolder%" ( + IF "%PathType%" NEQ "Directory" ( + DEL /Q "%LibFolder%" + MKDIR "%LibFolder%" + ) +) ELSE (MKDIR "%LibFolder%") +SET PathType= +REM - Fetch Script Items +COPY /Y "%DeployFolder%*" "%TempFolder%" >NUL +COPY /Y "%DeployLib%\*" "%LibFolder%" >NUL +IF %ERRORLEVEL% EQU 0 ( + REM - Launch Agent Setup Script + IF %ArgCount% EQU 0 ( + GOTO PARTNERCONFIG + ) + IF %ArgCount% EQU 1 ( + GOTO CUSTOMERIDONLY + ) + IF %ArgCount% EQU 2 ( + GOTO CUSTOMERANDTOKEN + ) +) ELSE ( + SET "Message=%SetupScript% was missing or not found after transfer." + GOTO QuitFailure +) + +:CUSTOMERANDTOKEN +ECHO Running with customer and token +START "" %WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File "%TempFolder%\InstallAgent.ps1" -CustomerID %CustomerID% -RegistrationToken %RegistrationToken% -LauncherPath "%DeployFolder% +IF %ERRORLEVEL% EQU 0 ( + GOTO QuitSuccess +) ELSE ( + GOTO QuitFailure +) + +:CUSTOMERIDONLY +ECHO Running with Customer ID Only +START "" %WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File "%TempFolder%\InstallAgent.ps1" -CustomerID %CustomerID% -LauncherPath "%DeployFolder% +IF %ERRORLEVEL% EQU 0 ( + GOTO QuitSuccess +) ELSE ( + GOTO QuitFailure +) + +:PARTNERCONFIG +ECHO Using Partner config +START "" %WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File "%TempFolder%\InstallAgent.ps1" -LauncherPath "%DeployFolder% +IF %ERRORLEVEL% EQU 0 ( + GOTO QuitSuccess +) ELSE ( + GOTO QuitFailure +) + +:QuitIncompatible +ECHO X OS Not Compatible with either the Agent or the %SetupScript% +EVENTCREATE /T ERROR /ID 13 /L APPLICATION /SO "%LauncherScript%" /D "The OS is not compatible with the N-Central Agent or the %SetupScript%." >NUL +SET LegacyWait=1 +GOTO Done + +:QuitFailure +ECHO X Execution Failed - %SetupScript% Not Started (See Application Event Log for Details) +EVENTCREATE /T ERROR /ID 11 /L APPLICATION /SO "%LauncherScript%" /D "!Message!" >NUL +GOTO Cleanup + +:QuitSuccess +ECHO O %SetupScript% Launched Successfully +EVENTCREATE /T INFORMATION /ID 10 /L APPLICATION /SO "%LauncherScript%" /D "Agent Setup Launcher successful" >NUL +GOTO Done + +:Cleanup +RD /S /Q "%TempFolder%" 2>NUL + +:Done +ECHO == Launcher Finished == +ECHO Exiting... + +PING 192.0.2.1 -n 10 -w 1000 >NUL \ No newline at end of file diff --git a/Agent/LaunchInstaller.ps1 b/Agent/LaunchInstaller.ps1 new file mode 100644 index 0000000..af25a8b --- /dev/null +++ b/Agent/LaunchInstaller.ps1 @@ -0,0 +1,124 @@ +param ( + [Switch]$Monitor +) +### 1.0.0 on 2021-02-21 - David Brooks, Premier Technology Solutions +### 1.0.1 on 2021-03-23 - Robby Swartenbroekx, b-inside bv +#################################################################### +# - Adapted updated Batch file version of InstallAgent to PowerShell +Write-Host "CustomerID: $($args[0])" -ForegroundColor Green +Write-Host "Token: $($args[1])" -ForegroundColor Green +$CustomerID = $args[0] +$RegistrationToken = $args[1] +Write-Host "Monitor switch is present: $($Monitor.IsPresent)" + +# - Launcher Script Name +$LauncherScript = "Agent Setup PS Launcher" +# - Setup Script Name +$SetupScript = "Agent Setup PS Script" +if (-not [System.Diagnostics.EventLog]::SourceExists($LauncherScript)) { + New-EventLog -Source $LauncherScript -LogName Application +} + +# Device Info Table +$Device = @{} +#Get OS version information from WMI +$WMIos = Get-WmiObject Win32_OperatingSystem +[Version] $Device.OSBuild = +if ($null -eq $WMIos.Version) +#If unable to retrieve from WMI, get from CIM +{ (Get-CimInstance Win32_OperatingSystem).Version } +else +{ $WMIos.Version } +# Operating System Architecture +if ($Device.OSBuild -lt "6.1") { + Write-Host "OS Not Compatible with either the Agent or the $SetupScript" -ForegroundColor Red + Write-EventLog -EntryType Error -EventId 13 -LogName Application -Source $LauncherScript -Message "The OS is not compatible with the N-Central Agent or the $SetupScript." > $null + Exit 2 +} + +# Attempt to create local cache folder for InstallAgent +$TempFolder = "$env:windir\Temp\AGPO" +if (!(Test-Path $TempFolder)) { + New-Item $TempFolder -ItemType Directory -Force > $Null + if (!$?) { + Write-Host "Unable to create temp folder" -ForegroundColor Red + Write-EventLog -EntryType Error -EventId 13 -LogName Application -Source $LauncherScript -Message "$SetupScript is unable to create temp folder in $TempFolder for install" > $null + Exit 2 + } +} +# Copy contents to local cache folder +$DeployFolder = "$(Split-Path $MyInvocation.MyCommand.Path -Parent)" +Write-Host "Copying $DeployFolder to local cache in $TempFolder" +Copy-Item "$DeployFolder\*" "$TempFolder\" -Recurse -Force +Write-Host "Number of Arguments $($args.Count)" +switch ($args.Count) { + 0 { + # Only PartnerConfig.xml values will be used + Write-Host "Launching with no parameters" -ForegroundColor Green + $p = Start-Process -FilePath "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File $TempFolder\InstallAgent.ps1 -LauncherPath $DeployFolder\" -PassThru + break + } + 1 { + # CustomerID from script parameter has preference over PartnerConfig.xml, will failback to PartnerConfig.xml + Write-Host "Launching with CustomerID" -ForegroundColor Green + $p = Start-Process -FilePath "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File $TempFolder\InstallAgent.ps1 -CustomerID $CustomerID -LauncherPath $DeployFolder\" -PassThru + break + } + 2 { + # Partner token from script parameter has preference over PartnerConfig.xml, will failback to partnerconfig.xml + Write-Host "Launching with CustomerID and Token" -ForegroundColor Green + $p = Start-Process -FilePath "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File $TempFolder\InstallAgent.ps1 -CustomerID $CustomerID -RegistrationToken $RegistrationToken -LauncherPath $DeployFolder\" -PassThru + break + } +} +# Successfully launched... + +if ($null -eq $p) { + Write-EventLog -EntryType Error -EventId 13 -LogName Application -Source $LauncherScript -Message "$SetupScript encountered an error starting the launcher" > $null + Exit 2 +} +else { + Write-Host "Successfully launched $TempFolder\InstallAgent.ps1 with $($args.Count) arguments" -ForegroundColor Green + Write-EventLog -EntryType Information -EventId 10 -LogName Application -Source $LauncherScript -Message "Successfully launched $TempFolder\InstallAgent.ps1 with $($args.Count) arguments" > $null + + if ($Monitor.IsPresent) { + + Write-Host "Launched InstallAgent with PID: $($p.Id), waiting on Exit" + $RegPaths = @{ + Summary = "HKLM:\SOFTWARE\N-able Community\InstallAgent" + Installation = "HKLM:\SOFTWARE\N-able Community\InstallAgent\Installation" + Diagnosis = "HKLM:\SOFTWARE\N-able Community\InstallAgent\Diagnosis" + } + + while (-not $p.HasExited) { + Start-Sleep 1 + Clear-Host + if (Test-Path $RegPaths.Summary) { + Write-Host "Progress: " -ForegroundColor Green -NoNewline + Get-ItemProperty $RegPaths.Summary | Select-Object * -ExcludeProperty PS* | Format-List * + } + } + if ($p.ExitCode -eq 0) { + Write-Host "Script ran successfully, displaying registry results:" -ForegroundColor Green + $RegPaths.Keys | ForEach-Object { + if (Test-Path $RegPaths[$_]) { + Write-Host "$($_): " -ForegroundColor Green -NoNewline; + Get-ItemProperty $RegPaths[$_] | Select-Object * -ExcludeProperty PS* | Format-List * + } + } + Write-Host "Check logs for additional details" + } + else { + Write-Host "Script ran successfully, displaying registry results:" + $RegPaths.Keys | ForEach-Object { + if (Test-Path $RegPaths[$_]) { + Write-Host "$($_): " -ForegroundColor Green -NoNewline; + Get-ItemProperty $RegPaths[$_] | Select-Object * -ExcludeProperty PS* | Format-List * + } + } + Write-Host "Check logs for additional details" + Write-EventLog -EntryType Error -EventId 13 -LogName Application -Source $LauncherScript -Message "$SetupScript encountered an error starting the launcher" > $null + } + + } +} diff --git a/Agent/Lib/InstallAgent-Core.psm1 b/Agent/Lib/InstallAgent-Core.psm1 new file mode 100644 index 0000000..8e51c9d --- /dev/null +++ b/Agent/Lib/InstallAgent-Core.psm1 @@ -0,0 +1,3351 @@ +# Core Functions for the Agent Setup Script (InstallAgent.ps1) +# Last Revised: 2021-03-23 +# Module Version: 6.0.1 + +### INITIALIZATION FUNCTIONS +############################### + +function DebugGetMethods { + ### Function Body + ############################### + ### This function is not used during normal script operation, it is used for the validation during development + # Provides a Gridview of the install methods hashtable + $paramOrder = @( + @{n = 'Key'; e = { $key } }, + @{n = 'Name'; e = { $_.Name } }, + #@{n = 'Parameter'; e = { $_.Parameter } }, + @{n = 'Available'; e = { $_.Available } }, + @{n = 'Failed'; e = { $_.Failed } }, + @{n = 'Value'; e = { $_.Value } }, + @{n = 'Token'; e = { $_.Token } }, + @{n = 'Type'; e = { $_.Type } }, + @{n = 'Attempts'; e = { $_.Attempts } }, + @{n = 'MaxAttempts'; e = { $_.MaxAttempts } } + ) + $Install.MethodData.Keys | ForEach-Object { $key = $_; $Install.MethodData[$_] | ForEach-Object { [PSCustomObject]$_ } } | Select-Object $paramOrder | Sort-Object Key | Out-GridView +} + +function DebugGetAppliance { + ### Function Body + ############################### + ### This function is not used during normal script operation, it is used for the validation during development + # Provides a Gridview of the installed agent and historicall installs + $paramOrder = @( + @{n = 'Key'; e = { $key } }, + @{n = 'ID'; e = { $_.ID } }, + @{n = 'WindowsVersion'; e = { $_.WindowsVersion } }, + @{n = 'Version'; e = { $_.Version } }, + @{n = 'SiteID'; e = { $_.SiteID } }, + @{n = 'AssignedServer'; e = { $_.AssignedServer } }, + @{n = 'LastInstall'; e = { $_.LastInstall } }, + @{n = 'ActivationKey'; e = { $_.ActivationKey } } + ) + + $keys = @("Appliance", "History") + $ + + Agent.Keys | Where-Object { $keys -contains $_ } | ForEach-Object { $key = $_; $Agent[$_] } | ForEach-Object { [PSCustomObject]$_ } | Select-Object $paramOrder | Sort-Object Key | Out-GridView +} + +function DebugGetProxyTokens { + ### Function Body + ############################### + ### This function is not used during normal script operation, it is used for the validation during development + # Function submits all CustomerIDs through the RequestAzWebProxyToken function to retrieve the registration token + # Note this updates the InstallMethod.Value param, so it may prevent further execution depend on debug point + # To reset, use DiagnoseAgent and GetInstallMethods functions + $SC.InstallMethods.UsesAzProxy.Keys | ForEach-Object { + if ($SC.InstallMethods.UsesAzProxy[$_]) { + $Install.ChosenMethod = $Install.MethodData.$_ + if ($null -ne $Install.ChosenMethod.Value) { + RequestAzWebProxyToken + } + } + } +} + +function WriteKey { + ### Parameters + ############################### + param ($Key, $Properties) + ### Function Body + ############################### + # Create the Key if Missing + if ((Test-Path $Key) -eq $false) + { New-Item -Path $Key -Force >$null } + # Add Properties and Assign Values + $Properties.Keys | + ForEach-Object { New-ItemProperty -Path $Key -Name $_ -Value $Properties.$_ -Force >$null } +} + +function AlphaValue { + ### Parameters + ############################### + param ($Value, [Switch] $2Digit) + ### Function Body + ############################### + if ($2Digit.IsPresent -eq $true) + { return ("A" + [String]([Char]($Value - 35))) } + else + { return ([String]([Char]($Value + 65))) } +} + +function Quit { + ### Parameters + ############################### + param ($Code) + ### Function Body + ############################### + ### Update the Script Registry Keys + # Assign the Appropriate Error Message + switch ($Code) { + # Successful Execution + 0 { + $Script.Execution.ScriptAction = $SC.SuccessScriptAction + $LCode = AlphaValue $Code + break + } + # Documented Typical Error + { @(1..25) -contains $_ } + { $LCode = AlphaValue $Code; break } + # Documented Internal Error + { @(100..125) -contains $_ } + { $LCode = AlphaValue $Code -2Digit; break } + # Undocumented Error + Default + { $Code = 999; $LCode = "Error"; break } + } + $Comment = $SC.Exit.$LCode.ExitResult + ### Publish Execution Results to the Registry + # Create a New Registry Key for Script Results and Actions + if ((Test-Path ($Script.Results.ScriptKey)) -eq $false) + { New-Item $($Script.Results.ScriptKey) -Force >$null } + # Collect Final Execution Values + $Script.Execution.ScriptResult = $Comment + $Script.Execution.ScriptExitCode = $Code + # Write Final Execution Values + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Append the Function Info if a Validation Error Occurs + if ($Code -match $SC.Validation.InternalErrorCode) { + $Script.Results.Function = $Function.Name + $Script.Results.LineNumber = $Function.LineNumber + $Script.Results.Details += @("`n== Error Details ==") + $Script.Results.Details += @($SC.Exit.$LCode.ExitMessage) + $Script.Results.Details += + if ($null -ne $Script.Results.Function) + { @("Function Name: " + $Script.Results.Function) } + $Script.Results.Details += + if ($null -ne $Script.Results.LineNumber) + { @("Called at Line: " + $Script.Results.LineNumber) } + $Script.Results.Details += + if ($null -ne $Script.Results.Parameter) + { @("Parameter Name: " + $Script.Results.Parameter) } + $Script.Results.Details += + if ($null -ne $Script.Results.GivenParameter) + { @("Given Parameter: " + $Script.Results.GivenParameter) } + } + ### Format the Message Data for the Event Log + # Add the Overall Script Result + $Script.Results.EventMessage += @("Overall Script Result: " + $SC.Exit.$LCode.ExitType + "`n") + # Add the Completion Status of Each Sequence + $Script.Sequence.Order | + ForEach-Object { + $Script.Results.EventMessage += @( + $_, + $Script.Sequence.Status[([Array]::IndexOf($Script.Sequence.Order, $_))] + ) -join ' - ' + } + # Add the Detailed Messages for Each Sequence + $Script.Results.EventMessage += @("`n" + ($Script.Results.Details -join "`n")) + # For Typical Errors, Add the Branded Error Contact Message from Partner Configuration + if ( + ($Code -ne 999) -and + ($Code -ne 0) -and + ($null -ne $Config.ErrorContactInfo) + ) { + $Script.Results.EventMessage += + "`n--=========================--", + "`nTo report this documented issue, please submit this Event Log entry to:`n", + $Config.ErrorContactInfo + } + # Combine All Message Items + $Script.Results.EventMessage = ($Script.Results.EventMessage -join "`n").TrimEnd('') + ### Publish Execution Results to the Event Log + # Create a New Key for the Event Source if Required + if ((Test-Path $Script.Results.ScriptEventKey) -eq $false) + { New-EventLog -Source $Script.Results.ScriptSource -LogName $Script.Results.EventLog } + # Write the Event + Write-EventLog -LogName $Script.Results.EventLog -Source $Script.Results.ScriptSource -EventID (9000 + $Code) -EntryType $Script.Results.ErrorLevel -Message $Script.Results.EventMessage -Category 0 + ### Cleanup Outdated Items + $SC.Paths.Old.Values | + ForEach-Object { Remove-Item $_ -Recurse -Force 2>$null } + ### Cleanup Working Folder + Remove-Item $Script.Path.InstallDrop -Force -Recurse 2>$null + if (!$DebugMode.isPresent) { + Remove-Item $Script.Path.TempFolder -Force -Recurse 2>$null + } + exit $Code +} + +function Log { + ### Parameters + ############################### + param + ( + $EventType, $Code, + $Message, $Sequence, + [Switch] $BeginSequence, + [Switch] $EndSequence, + [Switch] $Exit + ) + ### Parameter Validation + ############################### + # Notes: + # If you are ending a sequence, log the EndSequence with no other parameters + # Always submit an EventType, Code and Message if you are not calling the end sequence + if ($EndSequence.IsPresent -eq $true) + { <# Other Parameters are not Required #> } + else { + if ($BeginSequence.IsPresent -eq $true) { + # EventType and Code are not Required + $EventType = "Information" + $Code = 0 + } + else { + ### EventType - Must be a Valid Full or Partial Event Type + $EventType = + switch ($EventType) { + { "Information" -like ($_ + "*") } + { "Information"; break } + { "Warning" -like ($_ + "*") } + { "Warning"; break } + { "Error" -like ($_ + "*") } + { "Error"; break } + Default + { $null; break } + } + if ($null -eq $EventType) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "EventType" + $Script.Results.GivenParameter = $EventType + Quit 100 + } + } + ### Message - Must be a String + if ( + ($null -ne $Message) -and + (($Message -is [String]) -or + ($Message -is [Array])) + ) { + if ($Message -is [Array]) + { $Message = $Message -join "`n" } + } + else { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "Message" + Quit 100 + } + ### Sequence - Must be a String + if ($null -ne $Sequence) { + if ($Sequence -is [String]) + { $Script.Execution.ScriptSequence = $Sequence } + else { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "Sequence" + $Script.Results.GivenParameter = $Sequence + Quit 100 + } + } + else { + if ($null -eq $Script.Execution.ScriptSequence) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "Sequence" + $Script.Results.GivenParameter = "None Provided (Required Parameter when there is no Active Sequence)" + Quit 100 + } + } + } + ### Function Body + ############################### + if ($EndSequence.IsPresent -eq $false) { + ### Update Script Event Level + $Script.Results.ErrorLevel = + switch ($Script.Results.ErrorLevel) { + { $null -eq $_ } + { $EventType; break } + "Information" + { $EventType; break } + "Warning" + { if ($EventType -eq "Error") { $EventType }; break } + } + } + ### Update Script Sequence Results + # Update Sequence Order + if ($Script.Sequence.Order -notcontains $Script.Execution.ScriptSequence) + { $Script.Sequence.Order += @($Script.Execution.ScriptSequence) } + # Determine the Sequence Status + if ($BeginSequence.IsPresent -eq $true) { + $Status = $SC.SequenceStatus.C + # Add Sequence Header Before Detail Message + $Script.Results.Details += + ("--== " + $Script.Execution.ScriptSequence + " ==--"), + $Message + } + if (($BeginSequence.IsPresent -eq $false) -and ($EndSequence.IsPresent -eq $false)) { + $Status = + switch ($Code) { + 0 + { $Script.Sequence.Status[-1]; break } + { $null -eq $_ } + { ($SC.SequenceStatus.B + " (UNKNOWN)"); break } + Default + { ($SC.SequenceStatus.B + " ($Code)"); break } + } + # Add Detail Message to Current Sequence + $Script.Results.Details += @("`n" + $Message) + } + if ($EndSequence.IsPresent -eq $true) { + # Change Status to COMPLETE Unless Otherwise Specified + $Status = + if ($Script.Sequence.Status[-1] -eq $SC.SequenceStatus.C) + { $SC.SequenceStatus.A } else { $Script.Sequence.Status[-1] } + # Add Sequence Footer After Detail Message + $Script.Results.Details += @("--== " + $Script.Execution.ScriptSequence + " Finished ==--`n") + } + # Update the Event Log Sequence Status + $SelectedStatus = [Array]::IndexOf($Script.Sequence.Order, $Sequence) + switch (($Script.Sequence.Status).Count) { + { $_ -le $SelectedStatus } + { $Script.Sequence.Status += @($Status) } + Default + { ($Script.Sequence.Status)[$SelectedStatus] = $Status } + } + # Update the Registry Sequence Status if Required + if (@($BeginSequence.IsPresent, $EndSequence.IsPresent) -contains $true) + { WriteKey $Script.Results.ScriptKey $Script.Execution } + ### Terminate if Requested + if ($Exit.IsPresent -eq $true) { Quit $Code } +} + +function CatchError { + ### Parameters + ############################### + param ($Code, $Message, [Switch] $Exit) + ### Function Body + ############################### + # Add a Message if Found + if ($null -ne $Message) + { $Out = @($Message) } + else + { $Out = @("The Script encountered an undocumented error.") } + # Get Any Exception Info + if ($null -ne $ExceptionInfo) { + if ($null -ne $InvocationInfo.Line) { + $ExcCmd = ($InvocationInfo.Line).Replace("-ErrorAction Stop", "").Trim() + $ExcCmdLN = $InvocationInfo.ScriptLineNumber + } + $ExcLookup = $ExceptionInfo.InnerException + do { + $ExcMsg += @($ExcLookup.Message) + $ExcLookup = $ExcLookup.InnerException + } while ($null -ne $ExcLookup) + $ExcMsg | + ForEach-Object { if ($null -ne $_) { $ExcMsgItems++ } } + if (-not ($ExcMsgItems -gt 0)) + { $ExcMsg = @($ExceptionInfo.Message) } + if (($null -ne $ExcCmd) -and ($null -ne $ExcCmdLN)) { + $Out += + "== Command Details ==", + ("Faulting Line Number: " + $ExcCmdLN), + ("Faulting Command: " + $ExcCmd + "`n") + } + $Out += + "== Error Message ==", + ($ExcMsg -join "`n") + } + Log E $Code $Out + if ($Exit.IsPresent -eq $true) { Quit $Code } +} + +function GetDeviceInfo { + ### Function Body + ############################### + ### Computer Details + $WMIinfo = Get-WmiObject Win32_ComputerSystem + $WMIos = Get-WmiObject Win32_OperatingSystem + # Device Name + [String] $Device.Hostname = & "$env:windir\SYSTEM32\HOSTNAME.EXE" + [String] $Device.Name = $WMIinfo.Name + # Domain Role + [String] $Device.FQDN = $WMIinfo.Domain + $Device_Flags = + "Role", "IsWKST", "IsSRV", + "IsDomainJoined", "IsDC", "IsBDC" + $Device_Values = + switch ($WMIinfo.DomainRole) { + 0 + { @("Standalone Workstation", $true, $false, $false, $false, $false); break } + 1 + { @("Domain/Member Workstation", $true, $false, $true, $false, $false); break } + 2 + { @("Standalone Server", $false, $true, $false, $false, $false); break } + 3 + { @("Domain/Member Server", $false, $true, $true, $false, $false); break } + 4 + { @("Domain Controller (DC)", $false, $true, $true, $true, $false); break } + 5 + { @("Baseline Domain Controller (BDC)", $false, $true, $true, $true, $true); break } + Default + { $null; break } + } + if ($null -ne $Device_Values) { + for ($i = 0; $i -lt $Device_Flags.Count; $i++) + { $Device.($Device_Flags[$i]) = $Device_Values[$i] } + } + # Last Boot Time + [DateTime] $Device.LastBootTime = + if ($null -ne $WMIos.LastBootUpTime) + { Get-Date ($WMIos.ConvertToDateTime($WMIos.LastBootUpTime)) -UFormat "%Y-%m-%d %r" } else { 0 } + ### OS/Software Details + # PowerShell Version + [Version] $Device.PSVersion = + if ($null -ne $PSVersionTable) + { $PSVersionTable.PSVersion } else { "1.0" } + # Operating System Name/Build + [String] $Device.OSName = ($WMIos.Caption).Trim().Replace("Microsoftr", "Microsoft").Replace("Serverr", "Server").Replace("Windowsr", "Windows") + [Version] $Device.OSBuild = + if ($null -eq $WMIos.Version) + { ($PSVersionTable.BuildVersion.ToString()) } + else + { $WMIos.Version } + # Operating System Architecture + [String] $Device.Architecture = + if ($Device.OSBuild -le "6.0") { + if ($Device.OSName -like "*64*") + { "64-bit" } else { "32-bit" } + } + else { + if ($WMIos.OSArchitecture -like "*64*") + { "64-bit" } else { "32-bit" } + } + # Program Files Location + [String] $Device.PF32 = + if ($Device.Architecture -eq "64-bit") + { ($env:SystemDrive + "\Program Files (x86)") } + else + { ($env:SystemDrive + "\Program Files") } + [String] $Device.PF = ($env:SystemDrive + "\Program Files") + # Server Core Installation + $CoreSKUs = @(12..14), 29, @(39..41), @(43..46), 53, 63, 64 + [Boolean] $Device.ServerCore = + if ($CoreSKUs -contains $WMIos.OperatingSystemSKU) + { $true } else { $false } +} + +function GetNETVersion { + ### Function Body + ############################### + ### Retrieve .NET Framework Version + $NETinfo = + Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse | + Get-ItemProperty -Name Version, Release -ErrorAction SilentlyContinue | + Where-Object { $_.PSChildName -eq "Full" } | + Select-Object Version, Release, + @{ + Name = "Product" + Expression = { + $NET = $_ + switch ($NET) { + { $_.Version -eq "4.0.30319" } + { "4.0"; break } + { $_.Release -eq 378389 } + { "4.5"; break } + { @(378675, 378758) -contains $_.Release } + { "4.5.1"; break } + { $_.Release -eq 379893 } + { "4.5.2"; break } + { @(393295, 393297) -contains $_.Release } + { "4.6"; break } + { @(394254, 394271) -contains $_.Release } + { "4.6.1"; break } + { @(394802, 394806) -contains $_.Release } + { "4.6.2"; break } + { @(460798, 460805) -contains $_.Release } + { "4.7"; break } + { @(461308, 461310) -contains $_.Release } + { "4.7.1"; break } + { @(461808, 461814) -contains $_.Release } + { "4.7.2"; break } + { @(528040, 528049) -contains $_.Release } + { "4.8"; break } + { $_.Release -gt 528049 } + { "4.8"; $DisplayProduct = "4.8 or Newer"; break } + Default { + $NETVer = $NET.Version.Split(".") + $Product = @($NETVer[0], $NETVer[1]) -join '.' + $Product + $DisplayProduct = ($Product + " (Unverified)") + break + } + } + } + } + ### Summarize Version Info + if ($null -ne $NETinfo) { + $Device.NETVersion = [Version] (ValidateVersion $($NETinfo.Version)) + $Device.NETProduct = [Version] (ValidateVersion $($NETinfo.Product)) + $Device.NETDisplayProduct = + if ($null -ne $DisplayProduct) + { $DisplayProduct } else { $Device.NETProduct } + } +} + +function SelfElevate { + ### Function Body + ############################### + $CU_ID = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $CU_Principal = New-Object System.Security.Principal.WindowsPrincipal($CU_ID) + $Admin_Role = [System.Security.Principal.WindowsBuiltInRole]::Administrator + if (-not $CU_Principal.IsInRole($Admin_Role)) { + $Proc = New-Object System.Diagnostics.ProcessStartInfo "PowerShell" + $Proc.Arguments = ("-ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File `"" + $Script.Invocation + "`"") + $Script.Parameters.Keys | + ForEach-Object { + $i = $_ + $Proc.Arguments += + if ($Script.Parameters.$i -like "* *") + { (" -" + $i + " `"" + $Script.Parameters.$i) } + else + { (" -" + $i + " " + $Script.Parameters.$i) } + } + if ($Device.OSBuild -gt "6.0") { $Proc.Verb = "runas" } + [System.Diagnostics.Process]::Start($Proc) >$null + exit + } +} + +### VALIDATION FUNCTIONS +############################### + +function ValidateVersion { + ### Parameters + ############################### + param ($Version, $Digits) + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Parameter Validation + ############################### + # Version - Must be a Valid Version String + $Version = [String] $Version + if ($Version -notmatch $SC.Validation.VersionNumber.Accepted) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "Version" + $Script.Results.GivenParameter = ($Version + " - Must be a Valid Version String") + Quit 100 + } + # Places - Must be an Integer between 2 and 4 + if ($null -eq $Digits) + { $Digits = 4 } + else { + if ($Digits -notmatch $SC.Validation.VersionNumberDigits) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "Digits" + $Script.Results.GivenParameter = ([String]($Digits) + " - Must be an Integer (2-4)") + Quit 100 + } + } + ### Function Body + ############################### + $NewVersion = $Version + $VerCount = $Version.Split('.').Count + while ($VerCount -lt $Digits) { + $NewVersion += ".0" + $VerCount = ($NewVersion).Split('.').Count + } + while ($VerCount -gt $Digits) { + if ($NewVersion -eq $NewVersion.TrimEnd('0').TrimEnd('.')) + { break } else { $NewVersion = $NewVersion.TrimEnd('0').TrimEnd('.') } + $VerCount = $NewVersion.Split('.').Count + } + return $NewVersion +} + +function ValidateItem { + ### Parameters + ############################### + param ($Path, [Switch] $Folder, [Switch] $NoNewItem, [Switch] $RemoveItem) + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Parameter Validation + ############################### + # Path - Must be a Local Absolute Path + $Path | + ForEach-Object { + if ($_ -notmatch $SC.Validation.LocalFolderPath) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "Path" + $Script.Results.GivenParameter = ($Version + " (Must be a Local Absolute Path)") + Quit 100 + } + } + ### Function Body + ############################### + $RequiredType = + if ($Folder.IsPresent -eq $true) + { "Container" } else { "Leaf" } + $ImposterType = + if ($Folder.IsPresent -eq $true) + { "Leaf" } else { "Container" } + $NewItemType = + if ($Folder.IsPresent -eq $true) + { "Directory" } else { "File" } + $Path | + ForEach-Object { + $p = $_ + # Check for and Remove Imposters + if ((Test-Path $p -PathType $ImposterType) -eq $true) + { Remove-Item $p -Recurse -Force 2>$null } + if ($NoNewItem.IsPresent -eq $false) { + # Create an Empty Item if Required + if ((Test-Path $p) -eq $false) + { New-Item $p -ItemType $NewItemType -Force >$null 2>$null } + } + $ValidateResult += + if ($RemoveItem.IsPresent -eq $true) { + Remove-Item $p -Recurse -Force 2>$null + @((Test-Path $p) -eq $false) + } + else + { @((Test-Path $p -PathType $RequiredType) -eq $true) } + } + return $ValidateResult +} + +function ValidatePartnerConfig { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = "Validating Partner Configuration" + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Branding Values + $Partner.Config.Branding.GetEnumerator() | + Where-Object { $_.Name -ne '#comment' } | + ForEach-Object { $Config.$($_.Name) = $_.'#text' } + ### Script Behavior Values + $Partner.Config.ScriptBehavior.GetEnumerator() | + Where-Object { $_.Name -ne '#comment' } | + ForEach-Object { $Config.$($_.Name) = $_.'#text' } + ### Server Values + $Partner.Config.Server.GetEnumerator() | + Where-Object { $_.Name -ne '#comment' } | + ForEach-Object { $Config.$($_.Name) = $_.'#text' } + ### Service Behavior Values + $Partner.Config.ServiceBehavior.GetEnumerator() | + Where-Object { $_.Name -ne '#comment' } | + ForEach-Object { $Config.$("Service" + $_.Name) = $_.'#text' } + ### Deployment Values + $Config.LocalFolder = $Partner.Config.Deployment.LocalFolder + $Config.NetworkFolder = $Partner.Config.Deployment.NetworkFolder + # Installer Values + if ($Device.OSBuild -ge "6.1") { + # Use Typical (Latest) Agent + $InstallInfo = $Partner.Config.Deployment.Typical + } + else { + # Use Legacy Agent (Retain Support for Windows XP/Server 2003) + # Legacy support no longer available, error out + $InstallInfo = $Partner.Config.Deployment.Legacy + $Out = "Name-Central Agent for Windows is no longer supported on Vista/2008 and earlier" + Log E 19 $Out -Exit + } + $Config.InstallFolder = $InstallInfo.InstallFolder + $Config.AgentFile = $InstallInfo.SOAgentFileName + $Config.AgentVersion = $InstallInfo.SOAgentVersion + $Config.AgentFileVersion = $InstallInfo.SOAgentFileVersion + $Config.CustomerId = $InstallInfo.CustomerId + $Config.RegistrationToken = $InstallInfo.RegistrationToken + $Config.AzNableProxyUri = $InstallInfo.AzNableProxyUri + $Config.AzNableAuthCode = $InstallInfo.AzNableAuthCode + $Config.NETFile = $InstallInfo.NETFileName + $Config.NETVersion = $InstallInfo.NETVersion + $Config.NETFileVersion = $InstallInfo.NETFileVersion + $Config.EnforceBehaviorPolicy = if ($Partner.Config.ServiceBehavior.EnforcePolicy -like "True") {$true} else {$false} + $Config.ForceAgentCleanup = if ($Partner.Config.ScriptBehavior.ForceAgentCleanup -like "True") {$true} else {$false} + $Config.UseWSDLVerifcation = if ($Partner.Config.ScriptBehavior.UseWSDLVerification -like "True") {$true} else {$false} + + ### Function Body + ############################### + ### Validate Required Items from Partner Configuration + $ConfigUpdate = @{} + $Config.Keys | + ForEach-Object { + $i = $_ + $Invalid = + switch ($i) { + # Branding Values + "ErrorContactInfo" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # No Validation for Branding + $false + break + } + # Script Behavior Values + "BootTimeWaitPeriod" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Number from 0 to 60 + if ( + ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto2Digit) -or + ( + ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto2Digit) -and + ((([Int] $ConfigUpdate.$i) -lt 0) -or (([Int] $ConfigUpdate.$i) -gt 60)) + ) + ) + { $true } + else { + $false + # Convert Minutes to Seconds + $ConfigUpdate.$i = [Int] $ConfigUpdate.$i * 60 + } + break + } + "InstallTimeoutPeriod" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Number from 1 to 60 + if ( + ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto2Digit) -or + ( + ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto2Digit) -and + ((([Int] $ConfigUpdate.$i) -lt 1) -or (([Int] $ConfigUpdate.$i) -gt 60)) + ) + ) + { $true } + else { + $false + # Convert to Integer + $ConfigUpdate.$i = [Int] $ConfigUpdate.$i + } + break + } + # Server Values + "NCServerAddress" { + # Remove Outlying Blanks/Slashes if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim('/ ') } + # Remove Protocol Header if Present + if ($ConfigUpdate.$i -match $NC.Validation.ServerAddress.Accepted) + { $ConfigUpdate.$i = ($ConfigUpdate.$i).Split('/')[2] } + # Must be a Valid Server Address + if ($ConfigUpdate.$i -notmatch $NC.Validation.ServerAddress.Valid) + { $true } else { $false } + break + } + "PingCount" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Number from 1 to 100 + if ( + ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto3Digit) -or + ( + ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto3Digit) -and + ((([Int] $ConfigUpdate.$i) -lt 1) -or (([Int] $ConfigUpdate.$i) -gt 100)) + ) + ) + { $true } + else { + $false + # Convert to Integer + $ConfigUpdate.$i = [Int] $ConfigUpdate.$i + } + break + } + "PingTolerance" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Number from 0 to 100 + if ( + ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto3Digit) -or + ( + ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto3Digit) -and + ((([Int] $ConfigUpdate.$i) -lt 0) -or (([Int] $ConfigUpdate.$i) -gt 100)) + ) + ) + { $true } + else { + $false + # Convert to Integer + $ConfigUpdate.$i = [Int] $ConfigUpdate.$i + } + break + } + "ProxyString" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # No Validation for Proxy String + $false + break + } + # Service Behavior Values + { $_ -match '^ServiceAction.$' } { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be one of these Values and No Previous Service Actions can be Empty + if ( + ( + ($null -ne $ConfigUpdate.$i) -and + ($ConfigUpdate.$i -ne 'RESTART') -and + ($ConfigUpdate.$i -ne 'RUN') -and + ($ConfigUpdate.$i -ne 'REBOOT') + ) -or + ( + ($i -eq 'ActionB') -and + ($null -ne $ConfigUpdate.$i) -and + ($null -eq $ConfigUpdate.$($i -creplace ('B$', 'A'))) + ) -or + ( + ($i -eq 'ActionC') -and + ($null -ne $ConfigUpdate.$i) -and + ( + ($null -eq $ConfigUpdate.$($i -creplace ('C$', 'A'))) -or + ($null -eq $ConfigUpdate.$($i -creplace ('C$', 'B'))) + ) + ) + ) + { $true } else { $false } + break + } + "ServiceCommand" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # No Validation for Command + $false + break + } + { $_ -match '^ServiceDelay.$' } { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Assume No Delay if there is No Corresponding Action + if ($null -eq $ConfigUpdate.$($i -creplace (('Delay' + $i[-1]), ('Action' + $i[-1])))) + { $ConfigUpdate.$i = $null } + # Must be a Number from 0 to 3600 + if ( + ($null -ne $ConfigUpdate.$i) -and + ( + ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto4Digit) -or + ( + ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto4Digit) -and + ((([Int] $ConfigUpdate.$i) -lt 0) -or (([Int] $ConfigUpdate.$i) -gt 3600)) + ) + ) + ) + { $true } + else { + $false + # Convert to Integer (Seconds to Milliseconds) + $ConfigUpdate.$i = + if ($null -ne $ConfigUpdate.$i) + { ([Int] $ConfigUpdate.$i) * 1000 } else { [Int] 0 } + } + break + } + "ServiceReset" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Number from 0 to 44640 + if ( + ($null -ne $ConfigUpdate.$i) -and + ( + ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto5Digit) -or + ( + ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto5Digit) -and + ((([Int] $ConfigUpdate.$i) -lt 0) -or (([Int] $ConfigUpdate.$i) -gt 44640)) + ) + ) + ) + { $true } + else { + $false + # Convert to Integer (Minutes to Seconds) + $ConfigUpdate.$i = + if ($null -ne $ConfigUpdate.$i) + { ([Int] $ConfigUpdate.$i) * 60 } else { [Int] 0 } + } + break + } + "ServiceStartup" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Accept Partial String Values + if ("Automatic" -like ($ConfigUpdate.$i + "*")) { + $j = $i.Replace('Startup', '') + $ConfigUpdate.$i = "Automatic" + $ConfigUpdate.$($j + 'QueryString') = "Auto" + $ConfigUpdate.$($j + 'RepairString') = "Auto" + $ConfigUpdate.$($j + 'RequireDelay') = $false + } + if ("Delay" -like ($ConfigUpdate.$i + "*")) { + $j = $i.Replace('Startup', '') + $ConfigUpdate.$i = "Delay" + $ConfigUpdate.$($j + 'QueryString') = "Auto" + $ConfigUpdate.$($j + 'RepairString') = "Delayed-Auto" + $ConfigUpdate.$($j + 'RequireDelay') = $true + } + # Must be one of these Values + if (@("Automatic", "Delay") -notcontains $ConfigUpdate.$i) + { $true } else { $false } + break + } + # Deployment Values + "AgentFile" { + # Remove Outlying Blanks/Periods if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim('. ') } + # Must be a Valid Executable File Name + if ($ConfigUpdate.$i -notmatch $SC.Validation.FileNameEXE) + { $true } else { $false } + break + } + "AgentFileVersion" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Version Number with up to 4 Digits + if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Accepted) + { $true } + else { + $false + # Fill Empty Trailing Values with Zeroes + if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Valid) + { $ConfigUpdate.$i = ValidateVersion $($ConfigUpdate.$i) } + # Convert to Version + $ConfigUpdate.$i = [Version] $ConfigUpdate.$i + } + break + } + "AgentVersion" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Version Number with up to 4 Digits + if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Accepted) + { $true } + else { + $false + # Fill Empty Trailing Values with Zeroes + if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Valid) + { $ConfigUpdate.$i = ValidateVersion $($ConfigUpdate.$i) } + # Convert to Version + $ConfigUpdate.$i = [Version] $ConfigUpdate.$i + } + break + } + "InstallFolder" { + # Remove Outlying Blanks/Periods if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim('. ') } + # Must be a Valid Folder Name + if ($ConfigUpdate.$i -notmatch $SC.Validation.ItemName) + { $true } else { $false } + break + } + "CustomerId" { + # CustomerId can be null in the case it isn't being used, validate on null + if ("" -eq $Config.$i) { $false; break } + # Remove Outlying Blanks/Periods if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim('. ') } + # Must be a Valid Folder Name + if ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto5Digit) + { $true } else { $false } + break + } + "RegistrationToken" { + # Registration token can be null in the case it isn't being used, validate on null + if ("" -eq $Config.$i) { $false; break } + # Remove Outlying Blanks/Periods if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim('. ') } + # Must be a Valid Folder Name + if ($ConfigUpdate.$i -notmatch $SC.Validation.GUID) + { $true } else { $false } + break + } + "LocalFolder" { + # Remove Trailing Slash if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim('\ ') } + # Must be a Local Absolute Path + if ($ConfigUpdate.$i -notmatch $SC.Validation.LocalFolderPath) + { $true } else { $false } + break + } + "NETFile" { + # Remove Outlying Blanks/Periods if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim('. ') } + # Must be a Valid Executable File Name + if ($ConfigUpdate.$i -notmatch $SC.Validation.FileNameEXE) + { $true } else { $false } + break + } + "NETFileVersion" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Version Number with up to 4 Digits + if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Accepted) + { $true } + else { + $false + # Fill Empty Trailing Values with Zeroes + if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Valid) + { $ConfigUpdate.$i = ValidateVersion $($ConfigUpdate.$i) } + # Convert to Version + $ConfigUpdate.$i = [Version] $ConfigUpdate.$i + } + break + } + "NETVersion" { + # Remove Outlying Blanks if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim() } + # Must be a Version Number with up to 4 Digits + if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Accepted) + { $true } + else { + $false + # Fill Empty Trailing Values with Zeroes + if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Valid) + { $ConfigUpdate.$i = ValidateVersion $($ConfigUpdate.$i) } + # Convert to Version + $ConfigUpdate.$i = [Version] $ConfigUpdate.$i + } + break + } + "NetworkFolder" { + # Remove Outlying Blanks/Periods if Present + $ConfigUpdate.$i = + if (@($null, '') -notcontains $Config.$i) + { ($Config.$i).Trim('. ') } + # Must be a Valid Folder Name + if ($ConfigUpdate.$i -notmatch $SC.Validation.ItemName) + { $true } else { $false } + break + } + } + if ($Invalid -eq $true) + { $InvalidConfig += @($i) } + } + # Update Config Table + $ConfigUpdate.Keys | + ForEach-Object { $Config.$_ = $ConfigUpdate.$_ } + # Report on any Invalid Configuration Items + if ($null -ne $InvalidConfig) { + $Out = + "One or more items in the Partner Configuration was invalid.`n", + "Please verify the following values:" + $InvalidConfig | + Sort-Object | + ForEach-Object { $Out += @($_) } + Log E 2 $Out -Exit + } + else { + $Out = @("All Required values in the Partner Configuration were successfully validated.") + Log I 0 $Out + } + # Note valid CustomerId and Registration token + if ( $null -ne $Config.CustomerId -and $null -ne $Config.RegistrationToken) { + $Out = @("Valid CustomerId and Registration token found in Partner Configuration") + Log I 0 $Out + } + if ($Config.AzNableProxyUri -ne "" -and $Config.AzNableAuthCode -ne "") { + $Out = @("AzNableProxyUri and AuthCode found") + $Config.IsAzNableAvailable = $true + Log I 0 $Out + } + else { + $Config.IsAzNableAvailable = $false + } + ### Populate Configuration History Location from Partner Configuration + if ((ValidateItem $Config.LocalFolder -Folder) -eq $false) { + # ERROR - Unable to Validate Configuration History Folder + CatchError 104 "The Script was unable to validate the Configuration History Folder." -Exit + } + $Agent.Path.History = @($Config.LocalFolder, $SC.Names.HistoryFile) -join '\' +} + +function ValidateExecution { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = "Determining Execution Mode" + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + ### Build Installation Source Paths + $Install.Sources = @{ + "Demand" = @{ "Path" = ($LauncherPath + $Config.InstallFolder) } + "Network" = @{ + "Path" = @( + "\", $Device.FQDN, "NETLOGON", + $Config.NetworkFolder, $Config.InstallFolder + ) -join '\' + } + "Sysvol" = @{ + "Path" = @( + "\", $Device.FQDN, "sysvol" , $Device.FQDN, "scripts", + $Config.NetworkFolder, $Config.InstallFolder + ) -join '\' + } + } + ### Determine Execution Mode + $Script.Execution.ScriptMode = + switch ($Install.Sources.Demand.Path) { + $Install.Sources.Network.Path + { if ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { $SC.ExecutionMode.B } else { $SC.ExecutionMode.A }; break } + $Install.Sources.Sysvol.Path + { if ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { $SC.ExecutionMode.B } else { $SC.ExecutionMode.A }; break } + Default + { $SC.ExecutionMode.A; break } + } + ### Log Execution Mode + WriteKey $Script.Results.ScriptKey $Script.Execution + # Wait if Device has Recently Booted + if ( + ($null -ne $Device.LastBootTime) -and + ($Device.LastBootTime -ge (Get-Date).AddSeconds( - ($Config.BootTimeWaitPeriod))) + ) { + # Update Execution Info + $Script.Execution.ScriptAction = "Waiting Before Diagnosis" + WriteKey $Script.Results.ScriptKey $Script.Execution + # Wait for Required Duration + [Int] $WaitTime = + $Config.BootTimeWaitPeriod - ( + ((Get-Date) - ([DateTime] $Device.LastBootTime)) | + Select-Object -ExpandProperty TotalSeconds + ) + $Out = + ("Windows has booted within the " + $Config.BootTimeWaitPeriod + "-second Wait Period specified in the Partner Config.`n"), + ("Waiting the remaining " + $WaitTime + " seconds before Diagnosis...") + Log I 0 $Out + Start-Sleep -Seconds $WaitTime + } +} + +### DIAGNOSIS FUNCTIONS +############################### + +function ReadXML { + ### Parameters + ################################ + param ($XMLContent, $XPath) + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Parameter Validation + ################################ + ### XMLContent - Must be Full XML Text or an Absolute Path to an XML File + # Validate Full XML Text + if ($XMLContent -isnot [Xml]) { + # Validate Absolute XML Path + if ($XMLContent -match $SC.Validation.LocalFilePathXML) { + # Verify File Exists + if ((Test-Path $XMLContent -PathType Leaf 2>$null) -eq $true) + { [Xml] $XMLContent = Get-Content $XMLContent } + else { + # ERROR - File Does Not Exist + $Script.Results.Parameter = "XMLContent" + $Script.Results.GivenParameter = ("[" + $XMLContent + "] - The XML File does not Exist") + Quit 104 + } + } + else { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "XMLContent" + $Script.Results.GivenParameter = "Must be Valid XML File Content OR Absolute Path to an XML File" + Quit 100 + } + } + # XPath - Must be an XML Path to an Existing Element (Document Element if omitted) + ## Start with /, remove trailing / + if ($null -eq $XPath) + { $XPath = @("", $XMLContent.DocumentElement.Name, "*") -join '/' } + else { + if ($XPath -notmatch $SC.Validation.XMLElementPath) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "XPath" + $Script.Results.GivenParameter = ("[" + $XPath + "] - Must be a Valid XML Path String (e.g. /MyConfig/Settings/*)") + Quit 100 + } + } + ### Function Body + ################################ + $Hash = @{} + # Collect the Properties in the Current Element + $XMLContent.SelectNodes($XPath) | + Where-Object { $_.IsEmpty -eq $false } | + ForEach-Object { + $Node = $_ + switch ($Node.ChildNodes | Select-Object -ExpandProperty NodeType) { + # Store Values from Current Element + "Text" + { $Hash.$($Node.Name) = $Node.ChildNodes | Select-Object -ExpandProperty Value; break } + # Iterate through Elements for more Values + "Element" + { $Hash.$($Node.Name) = ReadXML $XMLContent $(@(($XPath -split '/\*')[0], $Node.Name, '*') -join '/'); break } + } + } + return $Hash +} + +function WriteXML { + ### Parameters + ############################### + param ($XMLPath, $Root, $Values) + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Parameter Validation + ############################### + # XMLPath - Must be an Absolute Path + if ($XMLPath -notmatch $SC.Validation.LocalFilePathXML) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "XMLPath" + $Script.Results.GivenParameter = ("[" + $XMLPath + "] - Must be an Absolute File Path with XML Extension") + Quit 100 + } + # Root - Must be a Valid XML Element Name + if ($Root -notmatch $SC.Validation.XMLElementName) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "Root" + $Script.Results.GivenParameter = "Must be a Valid XML Element Name" + Quit 100 + } + # Values - Must be a Hashtable with a Single Root Key + if ($Values -isnot [Hashtable]) { + # ERROR - Invalid Parameter + $Script.Results.Parameter = "Values" + $Script.Results.GivenParameter = "Must be a Hashtable with a Single Root Key" + Quit 100 + } + ### Function Body + ############################### + ### Remove XML Document if Required + if ((Test-Path $XMLPath) -eq $true) + { Remove-Item $XMLPath -Force 2>$null } + ### Create a New XML Document with Provided Values + [Xml] $XMLDoc = New-Object System.Xml.XmlDocument + # Write the XML Declaration + $Declaration = $XMLDoc.CreateXmlDeclaration("1.0", "UTF-8", $null) + $XMLDoc.AppendChild($Declaration) >$null + # Write the Root Element + $RootElement = $XMLDoc.CreateElement($Root) + $XMLDoc.AppendChild($RootElement) >$null + # Write Elements + $Values.Keys | + Sort-Object | + ForEach-Object { + $Element = $XMLDoc.CreateElement($_) + $Text = $XMLDoc.CreateTextNode($Values.$_) + $Element.AppendChild($Text) >$null + $RootElement.AppendChild($Element) >$null + } + $XMLDoc.Save($XMLPath) +} + +function QueryServices { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + ### Get Agent Service / Process Info + $($Agent.Services.Data.Keys) | + ForEach-Object { + $s = $_ + # Get Service Status + $Agent.Services.Data.$s = Get-WmiObject Win32_Service -Filter "Name='$s'" + if ($null -ne $Agent.Services.Data.$s) { + # Validate Related Process Info + $P0 = ($Agent.Services.Data.$s.PathName).Split()[0] + $P1 = + switch ($P0) { + { ($_ -match '^"') -and ($_ -match '"$') } + { $P0.Trim('"'); break } + { $_ -match '^"' } + { ($Agent.Services.Data.$s.PathName).Split('"')[1]; break } + { $_ -match '\.\S{3,}$' } + { $P0; break } + Default + { $Agent.Services.Data.$s.PathName; break } + } + $p = $P1.Split('\')[-1] + # Report Process Status + $Agent.Processes.$s = @{ + "FilePath" = $P1 + "Name" = $p -replace '\.\S{3,}$', '' + "ID" = + if ($Agent.Services.Data.$s.ProcessID -eq 0) + { $null } else { $Agent.Services.Data.$s.ProcessID } + } + ### Get Service Failure Behavior + $FailureRaw = & SC.EXE QFAILURE $s 5000 + # Get Service Reset Period + $FailureReset = ( + ( + $FailureRaw | + Where-Object { $_ -like "*RESET_PERIOD*" } + ) -split (' : ') + )[-1] + # Get Service Failure Actions + $ActionIndex = + [Array]::IndexOf( + $FailureRaw, + ($FailureRaw | Where-Object { $_ -like "*FAILURE_ACTIONS*" }) + ) + if ($ActionIndex -ge 0) { + $FailureRaw[$ActionIndex..($ActionIndex + 2)] | + Where-Object { $_ -ne '' } | + ForEach-Object { + $F1 = ($_ -split (' : '))[-1] + $FailureActions += @(($F1 -split (' -- '))[0].Trim()) + $FailureDelays += @(((($F1 -split (' -- '))[-1] -split (' = '))[-1]).Split(' ')[0]) + } + } + # Get Service Failure Command (if present) + $FailureCommand = ( + ( + $FailureRaw | + Where-Object { $_ -like "*COMMAND_LINE*" } + ) -split (' : ') + )[-1] + if ($FailureCommand -eq '') + { $FailureCommand = $null } + # Report Failure Behavior Status + $Agent.Services.Failure.$s = @{ + "Actions" = @{} + "Command" = $FailureCommand + "Delays" = @{} + "Reset" = $FailureReset + } + for ($i = 0; $i -lt 3; $i++) { + $Agent.Services.Failure.$s.Actions.$(AlphaValue $i) = + if ($null -ne $FailureActions) + { $FailureActions[$i] } else { $null } + } + for ($i = 0; $i -lt 3; $i++) { + $Agent.Services.Failure.$s.Delays.$(AlphaValue $i) = + if ($null -ne $FailureDelays) + { $FailureDelays[$i] } else { $null } + } + } + else { + # Write Dummy Values for Process and Service Failure Behavior + $Agent.Processes.$s = $null + $Agent.Services.Failure.$s = $null + } + } + ### Report Current Status + # Agent Exectuables Exist + $Agent.Health.ProcessesExist = + if ( + ( + $Agent.Processes.GetEnumerator() | + Where-Object { $null -ne $_.Value.FilePath } | + ForEach-Object { (Test-Path $_.Value.FilePath -PathType Leaf) -eq $true } + ).Count -eq $Agent.Services.Data.Count + ) + { $true } else { $false } + # Agent Processes Running + $Agent.Health.ProcessesRunning = + if ( + ($Agent.Health.ProcessesExist -eq $true) -and + ( + ( + $Agent.Processes.GetEnumerator() | + ForEach-Object { $null -ne $_.Value.ID } + ) -notcontains $false + ) + ) + { $true } else { $false } + # Agent Services Exist + $Agent.Health.ServicesExist = + if ($Agent.Services.Data.Values -notcontains $null) + { $true } else { $false } + # Agent Services Failure Behavior Configured + if ($Config.EnforceBehaviorPolicy -eq $true) { + $Agent.Health.ServicesBehaviorCorrect = + if ( + ( + $Agent.Services.Failure.GetEnumerator() | + ForEach-Object { + switch ($_) { + { $null -eq $_.Value } + { $false; break } + { + ( + ( + ( + $_.Value.Actions.GetEnumerator() | + ForEach-Object { + if ($null -ne $_.Value) + { $_.Value.Split()[0] -eq $Config.$("ServiceAction" + $_.Name) } + else { $false } + } + ) -notcontains $false + ) -and + ( + ( + $_.Value.Delays.GetEnumerator() | + ForEach-Object { $_.Value -eq $Config.$("ServiceDelay" + $_.Name) } + ) -notcontains $false + ) -and + ($_.Value.Reset -eq $Config.ServiceReset) -and + ($_.Value.Command -eq $Config.ServiceCommand) + ) + } + { $true; break } + Default + { $false; break } + } + } + ) -notcontains $false + ) + { $true } else { $false } + } + else { + $Agent.Health.ServicesBehaviorCorrect = $true + } + # Agent Services Running + $Agent.Health.ServicesRunning = + if ( + ($Agent.Health.ServicesExist -eq $true) -and + ( + ( + $Agent.Services.Data.GetEnumerator() | + ForEach-Object { $_.Value.State -eq "Running" } + ) -notcontains $false + ) + ) + { $true } else { $false } + # Agent Services Startup Configured + $Agent.Health.ServicesStartupCorrect = + if ( + ( + $Agent.Services.Data.GetEnumerator() | + ForEach-Object { + switch ($_) { + { $null -eq $_.Value } + { $false; break } + { + ($_.Value.StartMode -eq $Config.ServiceQueryString) -and + ( + ($_.Value.DelayedAutoStart -eq $Config.ServiceRequireDelay) -or + @( + if ( + ( + Get-ItemProperty (@($SC.Paths.ServiceKey, $_.Value.Name) -join '\') 2>$null | + Select-Object -ExpandProperty DelayedAutoStart 2>$null + ) -eq 1 + ) + { $Config.ServiceRequireDelay -eq $true } else { $Config.ServiceRequireDelay -eq $false } + ) + ) + } + { $true; break } + Default + { $false; break } + } + } + ) -notcontains $false + ) + { $true } else { $false } +} + +function TestNCServer { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + # Ping Name-Central Server and Google DNS + for ($i = 1; $i -le $Config.PingCount; $i++) { + $PingNCTest += @(Test-Connection $Config.NCServerAddress -Count 1 -Quiet) + Start-Sleep -Milliseconds 300 + $PingGoogleTest += @(Test-Connection '8.8.8.8' -Count 1 -Quiet) + Start-Sleep -Milliseconds 300 + } + # Check if Results Pass the Partner Threshold + $NCSuccessRate = ($PingNCTest -like $true).Count / $PingNCTest.Count + $NCResult = + if ((($PingNCTest -like $true).Count / $PingNCTest.Count) -ge ($Config.PingTolerance / 100)) + { $true } else { $false } + $GoogleSuccessRate = ($PingGoogleTest -like $true).Count / $PingGoogleTest.Count + $GoogleResult = + if ($GoogleSuccessRate -ge ($Config.PingTolerance / 100)) + { $true } else { $false } + # Evaluate and Log Connectivity Check Result + switch ($NCResult) { + $false { + $Out = @( + switch ($GoogleResult) { + $false + { "Device appears not to have Internet connectivity at present.`n"; break } + $true + { ("Device appears to have Internet connectivity, but is unable to reliably connect to the " + $NC.Products.NCServer.Name + ".`n"); break } + }, + "The Script will assess and perform Offline Repairs where possible until connectivity is restored." + ) + Log W 0 $Out + } + $true { + $Out = @( + switch ($GoogleResult) { + $false { + switch ($NCSuccessRate) { + { $_ -lt 1 } { + # Warn on Dropped Packets + $Flag = "W" + ("Device appears to have connectivity to the " + $NC.Products.NCServer.Name + ", but is dropping some packets.") + break + } + 1 { + $Flag = "I" + ("Device has reliable connectivity to the " + $NC.Products.NCServer.Name + ".") + break + } + }, + "However, the general Internet Connectivity Test failed. It's possible Google DNS Server is experiencing issues at present." + break + } + $true { + switch ($NCSuccessRate) { + { $_ -lt 1 } { + # Warn on Dropped Packets + $Flag = "W" + ("Device appears to have connectivity to the " + $NC.Products.NCServer.Name + ", but is dropping some packets.") + break + } + 1 { + $Flag = "I" + ("Device has reliable connectivity to the " + $NC.Products.NCServer.Name + ".") + break + } + }, + "General Internet Connectivity is reliable." + break + } + } + ) + Log $Flag 0 $Out + break + } + } + # Check if Agent has Connectivity to Server in Partner Configuration + + if ($Config.UseWSDLVerifcation -and $NCResult -eq $false) { + $client = New-Object System.Net.WebClient + try { + $response = $client.DownloadString("https://$($Config.NCServerAddress)/dms2/services2/ServerEI2?wsdl") + $xmlResponse = [xml]$response + if ($xmlResponse.definitions.service.port.address.location -eq "https://$($Config.NCServerAddress)/dms2/services2/ServerEI2") { + $Flag = "W" + $Out = ("Device failed ping test, but succeeded on WSDL verification method for " + $NC.Products.NCServer.Name + ", script will proceed with online activities") + Log $Flag 0 $Out + } + } + catch { + $Flag = "W" + $Out = ("WSDL verification method for " + $NC.Products.NCServer.Name + "failed, Offline Repairs will be performed possible until connectivity is restored.") + Log $Flag 0 $Out + } + } + + $Install.NCServerAccess = + if ($null -ne $Flag) + { $true } else { $false } +} + +function NewEncodedKey ([string]$Server, [string]$ID, [string]$token) { + $DecodedActivationKey = "HTTPS://$($Server):443|$ID|1|$token|0" + [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($DecodedActivationKey)) +} + +function DiagnoseAgent { + ### Parameters + ############################### + param ([Switch] $NoLog, [Switch] $NoServerCheck) + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + switch ($Script.Sequence.Order[-1]) { + $SC.SequenceNames.D { + Log I 0 "Re-Checking Agent Health after Repair Action(s)..." + break + } + $SC.SequenceNames.E { + if ($NoLog.IsPresent -eq $false) + { Log I 0 "Re-Checking Agent Health after Install Action..." } + break + } + Default { + $Script.Execution.ScriptAction = "Diagnosing Existing Installation" + WriteKey $Script.Results.ScriptKey $Script.Execution + break + } + } + ### Function Body + ############################### + ### Initialize/Clear Diagnostics Tables + $Agent.Appliance = @{} + $Agent.Docs = @{ + $NC.Products.Agent.ApplianceConfig = @{} + $NC.Products.Agent.InstallLog = @{} + $SC.Names.HistoryFile = @{} + "Registry" = @{} + } + $Agent.Health = @{} + $Agent.History = @{} + ### Get Info About the Current Installation + # Review Appliance Configuration + if ((Test-Path $Agent.Path.ApplianceConfig -PathType Leaf) -eq $true) { + # Read Appliance Config XML + $Agent.Docs.$($NC.Products.Agent.ApplianceConfig) = ReadXML $Agent.Path.ApplianceConfig + # Read Server Config XML + $Agent.Docs.$($NC.Products.Agent.ServerConfig) = ReadXML $Agent.Path.ServerConfig + } + # Review Checker Log + if ((Test-Path $Agent.Path.Checker -PathType Leaf) -eq $true) { + # Retrieve Values + $NC.Products.Agent.InstallLogFields | + ForEach-Object { + $t_Found = Select-String ($_ + "\s+:") $Agent.Path.Checker + $Agent.Docs.$($NC.Products.Agent.InstallLog).$_ = + if ($null -ne $t_Found) + { ($t_Found.Line -split (' : '))[1].Trim() } else { $null } + } + } + # Detect and Review Registry Uninstall Key + $Agent.Path.Registry = ( + Get-ChildItem $NC.Paths.$("UninstallKey" + $Device.Architecture.Split('-')[0]) | + ForEach-Object { Get-ItemProperty $_.PSPath } | + Where-Object { $_.DisplayName -eq $NC.Products.Agent.WindowsName } + ).PSPath + if ($null -ne $Agent.Path.Registry) { + $RegistryTable = @{} + Get-ItemProperty $Agent.Path.Registry | + Get-Member -MemberType NoteProperty | + Select-Object -ExpandProperty Name | + ForEach-Object { $RegistryTable.Add($_, (Get-ItemProperty $Agent.Path.Registry).$_) } + $Agent.Docs.Registry = $RegistryTable + } + ### Get Info About Last Known Installation (in case Agent is Missing) + if ((Test-Path $Agent.Path.History -PathType Leaf) -eq $true) { + # Read Agent Configuration History XML + $Agent.Docs.$($SC.Names.HistoryFile) = ReadXML $Agent.Path.History + } + ### Validate All Discovered Info + $($Agent.Docs.Keys | Sort-Object) | + ForEach-Object { + $d = $_ + # Set the Appropriate Information Key for each Document + $($Agent.Docs.$d.Keys) | + ForEach-Object { + $i = $_ + # Set the Appropriate Table Keys, Value Names and Match Criteria + $AppInfo = + switch ($i) { + # Common Info + "Version" { + switch ($d) { + $SC.Names.HistoryFile + { @($i, $SC.Validation.VersionNumber.Valid); break } + Default + { $null; break } + } + break + } + # Appliance Unique Info + "ApplianceID" + { @("ID", $NC.Validation.ApplianceID); break } + "ApplianceVersion" + { @("Version", $SC.Validation.VersionNumber.Valid); break } + "CustomerID" + { @("SiteID", $NC.Validation.CustomerID); break } + # Checker Unique Info + "Activation Key" + { @("ActivationKey", $NC.Validation.ActivationKey.Encoded); break } + "Appliance ID" + { @("ID", $NC.Validation.ApplianceID); break } + "Customer ID" + { @("SiteID", $NC.Validation.CustomerID); break } + "Install Time" + { @("LastInstall", $null); break } + "Package Version" + { @("WindowsVersion", $SC.Validation.VersionNumber.Valid); break } + # History Unique Info + "ActivationKey" + { @($i, $NC.Validation.ActivationKey.Encoded); break } + "HistoryUpdated" + { @($i, $null); break } + "ID" + { @($i, $NC.Validation.ApplianceID); break } + "LastInstall" + { @($i, $null); break } + "ScriptSiteID" + { @($i, $NC.Validation.CustomerID); break } + "SiteID" + { @($i, $NC.Validation.CustomerID); break } + "WindowsVersion" + { @($i, $SC.Validation.VersionNumber.Valid); break } + # Registry Unique Info + "DisplayVersion" + { @($i, $SC.Validation.VersionNumber.Valid); break } + "InstallDate" + { @($i, $null); break } + "InstallLocation" + { @($i, $SC.Validation.LocalFolderPath); break } + "UninstallString" + { @($i, $null); break } + # Server Unique Info + "ServerIP" + { @("AssignedServer", $null); break } + Default + { $null; break } + } + # Apply Additional Formatting to Select Data + if ($null -ne $AppInfo) { + $FormattedInfo = + if ($null -ne $Agent.Docs.$d.$i) { + switch ($i) { + # Date Values + "InstallDate" { + if ($Agent.Docs.Registry.InstallDate.Length -eq 8) { + Get-Date ( + @( + -join $Agent.Docs.Registry.InstallDate[0..3], + -join $Agent.Docs.Registry.InstallDate[4..5], + -join $Agent.Docs.Registry.InstallDate[6..7] + ) -join '-' + ) -UFormat $SC.DateFormat.Short + } + else { + Get-Date -UFormat $SC.DateFormat.Short + } + } + { @("HistoryUpdated", "Install Time", "LastInstall") -contains $_ } { + try + { Get-Date $Agent.Docs.$d.$i -UFormat $SC.DateFormat.Full } + catch + { $null } + break + } + # InstallLocation Value + "InstallLocation" + { ($Agent.Docs.$d.$i).Trim('\ '); break } + # Version Values + { + @( + "ApplianceVersion", "DisplayVersion", "Package Version", + "Version", "WindowsVersion" + ) -contains $_ + } { + ValidateVersion $($Agent.Docs.$d.$i) + break + } + Default + { $Agent.Docs.$d.$i; break } + } + } + else { $null } + # Validate the Info + $ValidatedInfo = + if (($null -ne $FormattedInfo) -and ($FormattedInfo -match $AppInfo[1])) + { $FormattedInfo } else { $null } + if ($null -ne $ValidatedInfo) { + # Add Valid Info to Appliance/History Tables + if ($null -eq $Agent.$($SC.Validation.Docs.$d).$($AppInfo[0])) + { $Agent.$($SC.Validation.Docs.$d).$($AppInfo[0]) = $ValidatedInfo } + } + } + } + } + ### Build Activation Key/Appliance ID from Available Parts if Required + foreach ($t in @($($Agent.Appliance), $($Agent.History))) { + # Activation Key + if ($null -eq $t.ActivationKey) { + # Build Activation Key + $ChosenToken = + if ($null -ne $Install.ChosenMethod.Token) { + $Install.ChosenMethod.Token + } + elseif ($null -ne $Script.RegistrationToken) { + $Script.RegistrationToken + } + elseif ($null -ne $Config.RegistrationToken) { + $Config.RegistrationToken + } + + if ($null -ne $t.ID) { + $r = $($Agent) + $r.DecodedActivationKey = "HTTPS://$($Agent.Appliance.AssignedServer):443|$($t.ID)|1|$($ChosenToken)|0" + # Add the Value + $t.ActivationKey = + [System.Convert]::ToBase64String( + [System.Text.Encoding]::UTF8.GetBytes($r.DecodedActivationKey) + ) + } + } + } + + ### Build Activation Key ID from Available Parts if Required + ### Build activation key from script input and Appliance ID + # "Activation Key : Token (Current Script) / Appliance ID (Existing Installation)" + if ($Agent.Appliance.ID -and $Script.RegistrationToken) { + #Activation Key: Appliance Server/Appliance App ID/Script Token + $Script.ActivationKey = NewEncodedKey $Agent.Appliance.AssignedServer $Agent.Appliance.ID $Script.RegistrationToken + } + + ### Build activation key from Partner Config and Appliance ID + # "Activation Key : Token (Partner Config) / Appliance ID (Existing Installation)" + if ($Agent.Appliance.ID -and $Config.RegistrationToken) { + $Config.ActivationKey = NewEncodedKey $Agent.Appliance.AssignedServer $Agent.Appliance.ID $Config.RegistrationToken + } + ### Update or Create Historical Configuration File if Required + UpdateHistory + ### Check for a Corrupt or Disabled Agent + QueryServices + ### Check for a Missing Agent + # Agent is Installed + $Agent.Health.Installed = + if ( + ($null -ne $Agent.Path.Registry) -and + ((ValidateItem $(@($Device.PF32, $NC.Paths.BinFolder, $NC.Products.Agent.Process) -join '\') -NoNewItem) -eq $true) + ) { + if ((Test-Path $Agent.Path.Registry) -eq $true) + { $true } else { $false } + } + else { $false } + # Log Discovered Agent Status + if (($Agent.Health.Installed -eq $true) -and ($NoLog.IsPresent -eq $false)) { + $Out = @("Found:") + $Out += @( + switch ($Agent.Appliance) { + { ($null -ne $_.Version) } { + switch ($_.WindowsVersion) { + $null + { ($NC.Products.Agent.Name + " Version - " + $Agent.Appliance.Version) } + Default + { ($NC.Products.Agent.Name + " Version - " + $Agent.Appliance.Version + " (Windows " + $Agent.Appliance.WindowsVersion + ")") } + } + } + { $null -eq $_.Version } { + switch ($_.WindowsVersion) { + $null + { ($NC.Products.Agent.Name + " Version - (Windows " + $Agent.Registry.DisplayVersion + ")") } + Default + { ($NC.Products.Agent.Name + " Version - (Windows " + $Agent.Appliance.WindowsVersion + ")") } + } + } + { $null -ne $_.LastInstall } + { ("Installed on " + (Get-Date $Agent.Appliance.LastInstall -UFormat $SC.DateFormat.FullMessageOnly)) } + { $null -eq $_.LastInstall } + { ("Installed on " + $InstallDate) } + } + ) + Log I 0 -Message $Out + } + ### Check if Appliance ID is Invalid + $Agent.Health.ApplianceIDValid = + if ($Agent.Docs.$($NC.Products.Agent.ApplianceConfig).ApplianceID -match $NC.Validation.ApplianceID) + { $true } else { $false } + ### Check if Installed Agent is Up to Date + $Agent.Health.VersionCorrect = + if ($Agent.Health.Installed) { + if ( + ( + ($null -ne $Agent.Appliance.Version) -and + (([Version] $Agent.Appliance.Version) -ge ([Version] $Config.AgentVersion)) + ) -or + ( + ($null -ne $Agent.Appliance.WindowsVersion) -and + (([Version] $Agent.Appliance.WindowsVersion) -ge ([Version] $Config.AgentFileVersion)) + ) + ) + { $true } else { $false } + } + else { $false } + ### Verify Connectivity to Partner Server + if ($NoServerCheck.IsPresent -eq $false) + { TestNCServer } + ### Check if Installed Agent Server Address matches Partner Configuration + $Agent.Health.AssignedToPartnerServer = + if ($Agent.Appliance.AssignedServer -eq $Config.NCServerAddress) + { $true } else { $false } + ### Summarize Agent Health + # Update Status + $Agent.Health.AgentStatus = + switch ($Agent.Health) { + { $_.Values -notcontains $false } + { $SC.ApplianceStatus.A; break } + { $_.Installed -eq $false } + { $SC.ApplianceStatus.G; break } + { @($_.ProcessesExist, $_.ServicesExist) -contains $false } + { $SC.ApplianceStatus.F; break } + { $_.AssignedToPartnerServer -eq $false } { + if ($Agent.Appliance.AssignedServer -eq $NC.Products.Agent.ServerDefaultValue) + { $SC.ApplianceStatus.C } else { $SC.ApplianceStatus.E } + break + } + { @($_.ProcessesRunning, $_.ServicesRunning) -contains $false } + { $SC.ApplianceStatus.D; break } + { $_.ApplianceIDValid -eq $false } + { $SC.ApplianceStatus.C; break } + { + @( + $_.VersionCorrect, + $_.ServicesBehaviorCorrect, + $_.ServicesStartupCorrect + ) -contains $false + } + { $SC.ApplianceStatus.B; break } + } + # Update Registry Values + $Script.Execution.AgentLastDiagnosed = Get-Date -UFormat $SC.DateFormat.Full + WriteKey $Script.Results.ScriptDiagnosisKey $Agent.Health + # Identify/Log Needed Repairs + if ($NoLog.IsPresent -eq $false) { + $Out = @("Current Agent Status is " + $Agent.Health.AgentStatus + ":") + $Out += @( + switch ($Agent.Health.AgentStatus) { + $SC.ApplianceStatus.A + { "No Action Required"; break } + { @($SC.ApplianceStatus.E, $SC.ApplianceStatus.F, $SC.ApplianceStatus.G) -contains $_ } + { "Installation Required"; break } + { @($SC.ApplianceStatus.C, $SC.ApplianceStatus.D) -contains $_ } + { "Repair Required"; break } + $SC.ApplianceStatus.B { + switch ($Agent.Health) { + { $_.VersionCorrect -eq $false } + { "Agent Update Required" } + { $_.ServicesBehaviorCorrect -eq $false } + { "Service Failure Behavior Adjustment Required" } + { $_.ServicesStartupCorrect -eq $false } + { "Service Startup Type Adjustment Required" } + } + break + } + } + ) + Log I 0 $Out + } + # Determine Sequence Behavior After Diagnosis + if (($Agent.Health.AgentStatus -eq $SC.ApplianceStatus.A) -and ($Script.Sequence.Order[-1] -eq $SC.SequenceNames.C)) { + # No Further Action Required After Initial Diagnosis + Log -EndSequence + Log I -Code 0 -Message "No Further Action Required After Initial Diagnosis - Exiting" -Exit + } + # Proceed to Next Sequence or Return to Current Sequence +} + +### REPAIR FUNCTIONS +############################### + +function VerifyServices { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = + switch ($Script.Sequence.Order[-1]) { + $SC.SequenceNames.D + { "Verifying Service Repair"; break } + $SC.SequenceNames.E + { "Monitoring Agent Services Post-Install"; break } + } + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + # Check Current Service Status + QueryServices + # Verify Processes Have Not Terminated or Restarted for 2 Minutes + do { + $Agent.Services.Data.Keys | + ForEach-Object { + $s = $_ + $MatchID = $Agent.Processes.$s.ID + $ProcessFound = + if ($null -ne $MatchID) + { Get-Process -Id $MatchID 2>$null } + $ProcessMatch += @( + if ($null -ne $ProcessFound) { + if ($ProcessFound.ProcessName -eq $Agent.Processes.$s.Name) + { $true } else { $false } + } + else { $false } + ) + } + Start-Sleep 10 + } + while (($ProcessMatch -notcontains $false) -and ($ProcessMatch.Count -lt 12)) + # Complete/Fail Verification + if ($ProcessMatch.Count -ge 12) + { return $true } + else { + # Re-Check Current Service Status + QueryServices + # Re-check with the New PIDs (in case of Agent-issued Service Restart, Upgrade, etc.) + do { + $Agent.Services.Data.Keys | + ForEach-Object { + $s = $_ + $MatchID = $Agent.Processes.$s.ID + $ProcessFound = + if ($null -ne $MatchID) + { Get-Process -Id $MatchID 2>$null } + $ProcessMatch += @( + if ($null -ne $ProcessFound) { + if ($ProcessFound.ProcessName -eq $Agent.Processes.$s.Name) + { $true } else { $false } + } + else { $false } + ) + Start-Sleep 10 + } + } + while (($ProcessMatch -notcontains $false) -and ($ProcessMatch.Count -lt 12)) + # Complete/Fail Verification + if ($ProcessMatch.Count -ge 12) + { return $true } else { return $false } + } +} + +function FixServices { + ### Parameters + ############################### + param ([Switch] $Restart, [Switch] $Disable) + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + $Agent.Services.Data.Keys | + ForEach-Object { + $s = $_ + if ($Disable.IsPresent -eq $true) { + ### Stop and Disable the Services Instead + & SC.EXE CONFIG "$s" START= "Disabled" >$null 2>$null + & TASKKILL.EXE /PID $Agent.Processes.$s.ID /F >$null 2>$null + Get-Service -Name $s 2>$null | Stop-Service 2>$null -WarningAction SilentlyContinue + } + else { + ### Start or Restart the Service + try { + switch ($Agent.Services.Data.$s.State) { + "Running" { + # Service Running - Attempt to Restart Only if Specified + if ($Restart.IsPresent -eq $true) { + & TASKKILL.EXE /PID $Agent.Processes.$s.ID /F >$null 2>$null + if (@(0, 128) -contains $LASTEXITCODE) { + Get-Service -Name $s | Stop-Service 2>$null -WarningAction SilentlyContinue + Get-Service -Name $s | Start-Service 2>&1 -ErrorAction Stop + } + else { + # Fail the Repair if the Process cannot be Killed + $RepairResult = + if ($RepairResult -ne $false) + { $false } else { $RepairResult } + } + } + break + } + "Stopped" { + # Service Stopped - Attempt to Start + Get-Service -Name $s | Start-Service 2>&1 -ErrorAction Stop + break + } + Default { + # Service Pending Action or Not Responding - Kill Process and Attempt to Start + & TASKKILL.EXE /PID $Agent.Processes.$s.ID /F >$null 2>$null + if (@(0, 128) -contains $LASTEXITCODE) { + Get-Service -Name $s | Stop-Service 2>$null -WarningAction SilentlyContinue + Get-Service -Name $s | Start-Service 2>&1 -ErrorAction Stop + } + else { + # Fail the Repair if the Process cannot be Killed + $RepairResult = + if ($RepairResult -ne $false) + { $false } else { $RepairResult } + } + break + } + } + } + catch { + # Fail the Repair if the Service cannot Start + $RepairResult = + if ($RepairResult -ne $false) + { $false } else { $RepairResult } + } + } + } + if ($Disable.IsPresent -eq $false) { + # Re-Check Service/Process Status After Repair + $RepairResult = VerifyServices + # Complete the Repair unless it Otherwise Failed + $RepairResult = + if ($RepairResult -eq $true) + { $RepairResult } else { $false } + return $RepairResult + } +} + +function FixOrphanedAppliance { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + ### Replace the Appliance ID in the Appliance Configuration + [Xml] $XMLDoc = Get-Content $Agent.Path.ApplianceConfig + # Select the Most Recent Appliance ID to do the Replacement + $SelectedID = + switch ($Agent) { + { $null -ne $_.Appliance.ID } + { $Agent.Appliance.ID; break } + { $null -ne $_.History.ID } + { $Agent.History.ID; break } + Default { + # Fail the Repair - No Valid Appliance ID Found + return $false + } + } + $XMLDoc.ApplianceConfig.ApplianceID = $SelectedID + $XMLDoc.Save($Agent.Path.ApplianceConfig) + Remove-Item $Agent.Path.ApplianceConfigBackup -Force + ### Replace the Server IPs in the Server Configuration + [Xml] $XMLDoc = Get-Content $Agent.Path.ServerConfig + $XMLDoc.ServerConfig.ServerIP = $Config.NCServerAddress + $XMLDoc.ServerConfig.BackupServerIP = $Config.NCServerAddress + $XMLDoc.Save($Agent.Path.ServerConfig) + Remove-Item $Agent.Path.ServerConfigBackup -Force + return $true +} + +function FixStartupType { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + $Agent.Services.Data.Keys | + Sort-Object | + ForEach-Object { + $CurrentService = $_ + ### Ensure Service Startup is Configured Properly + & SC.EXE CONFIG "$CurrentService" START= $Config.ServiceRepairString >$null 2>$null + # Fail the Repair if Service Configuration is Unsuccessful + $RepairResult = + if ($RepairResult -ne $false) + { $LASTEXITCODE -eq 0 } else { $RepairResult } + } + return $RepairResult +} + +function FixFailureBehavior { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + $Agent.Services.Data.Keys | + Sort-Object | + ForEach-Object { + $CurrentService = $_ + ### Ensure Service Failure Behavior is Configured Properly + # Build Command String + $ActionsPart = @( + @("A", "B", "C") | + ForEach-Object { + $CurrentAction = $_ + @($Config.$("ServiceAction" + $CurrentAction), $Config.$("ServiceDelay" + $CurrentAction)) -join '/' + } + ) -join '/' + # Configure the Service + if ($null -ne $Config.ServiceCommand) + { & SC.EXE FAILURE "$CurrentService" ACTIONS= $ActionsPart RESET= $Config.ServiceReset COMMAND= $Config.ServiceCommand >$null 2>$null } + else + { & SC.EXE FAILURE "$CurrentService" ACTIONS= $ActionsPart RESET= $Config.ServiceReset >$null 2>$null } + # Fail the Repair if Service Configuration is Unsuccessful + $RepairResult = + if ($RepairResult -ne $false) + { $LASTEXITCODE -eq 0 } else { $RepairResult } + } + return $RepairResult +} + +function RepairAgent { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = "Performing Applicable Repairs" + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + ### Check if Installation is Required + $Out = + switch ($Agent.Health.AgentStatus) { + $SC.ApplianceStatus.G + { ("The " + $NC.Products.Agent.Name + " is currently not installed."); break } + $SC.ApplianceStatus.F + { ("The current " + $NC.Products.Agent.Name + " installation is damaged and must be re-installed."); break } + $SC.ApplianceStatus.E + { ("The current " + $NC.Products.Agent.Name + " installation is not authenticating with the Partner Name-Central Server and must be re-installed."); break } + { $Agent.Health.VersionCorrect -eq $false } + { ("The current " + $NC.Products.Agent.Name + " installation is out of date and must be upgraded."); break } + } + if ($null -ne $Out) { + # Skip Repair Sequence + $Script.Sequence.Status[-1] = $SC.SequenceStatus.D + Log I 0 ("The Repair Sequence was skipped. " + $Out) + } + else { + ### Select Repairs to Perform + switch ($Agent.Health) { + { $_.ApplianceIDValid -eq $false } + { $Repair.Required += @("A") } + { $_.ServicesStartupCorrect -eq $false } + { $Repair.Required += @("B") } + { $_.ServicesBehaviorCorrect -eq $false } + { $Repair.Required += @("C") } + { @($_.ProcessesRunning, $_.ServicesRunning) -contains $false } + { $Repair.Required += @("D") } + } + if ($null -eq $($Repair.Required)) { + # ERROR - Repairs Required but None Selected + Quit 101 + } + else { + ### Perform Selected Repairs + $Repair.Required | + ForEach-Object { + switch ($_) { + "A" { + # Repair Appliance ID + $Repair.Results.$($SC.Repairs.$_.Name) = FixOrphanedAppliance + } + "B" { + # Repair Service Startup Type + $Repair.Results.$($SC.Repairs.$_.Name) = FixStartupType + } + "C" { + # Repair Service Failure Behavior + $Repair.Results.$($SC.Repairs.$_.Name) = FixFailureBehavior + } + "D" { + # Restart Agent Services/Processes + $Repair.Results.$($SC.Repairs.$_.Name) = FixServices + } + } + } + ### Perform Post-Repair Actions + # Determine Required Actions + $Repair.Required | + ForEach-Object { + $PostRepairActions += @( + switch ($SC.Repairs.$_.PostRepairAction) { + $null { + # No Post-Repair Action + break + } + Default { + # Add the Action to the List + $_ + break + } + } + ) + } + # Perform Required Actions + if ($PostRepairActions -contains $SC.RepairActions.A) { + # Skip Recovery Actions since Installation is Required + $Script.Sequence.Status[-1] = $SC.SequenceStatus.E + } + else { + if ($PostRepairActions -contains $SC.RepairActions.B) { + # Restart the Agent Services + $Repair.Results.$($SC.Repairs.PostRepair.Name) = FixServices -Restart + } + ### Perform Recovery Actions if Required + # Determine Required Actions + $Repair.Results.Keys | + ForEach-Object { + $CurrentRepair = $_ + # Perform Recovery Actions only if the Repair Failed + if ($Repair.Results.$CurrentRepair -eq $false) { + $RecoveryActions += @( + switch ( + $SC.Repairs.GetEnumerator() | + Where-Object { $_.Value.Name -eq $CurrentRepair } | + ForEach-Object { $_.Value.RecoveryAction } + ) { + $null { + # No Recovery Action + break + } + Default { + # Add the Action to the List + $_ + break + } + } + ) + } + } + # Perform Required Actions + if ($RecoveryActions -contains $SC.RepairActions.A) { + # Skip Remaining Recovery Actions since Installation is Required + $Script.Sequence.Status[-1] = $SC.SequenceStatus.E + } + else { + if ($RecoveryActions -contains $SC.RepairActions.B) { + # Restart the Agent Services + $Repair.Results.$($SC.Repairs.Recovery.Name) = FixServices -Restart + } + } + } + } + ### Re-Check Agent Status After Repairs + DiagnoseAgent -NoLog -NoServerCheck + ### Summarize Repair Results + # Update Registry Values + $Script.Execution.AgentLastRepaired = Get-Date -UFormat $SC.DateFormat.Full + WriteKey $Script.Results.ScriptRepairKey $Repair.Results + # Log Detailed Repair Results + $Out = @("The following Repairs were attempted by the Script:") + $Out += + $Repair.Results.Keys | + Sort-Object | + ForEach-Object { + $CurrentRepair = $_ + $RepairStatus = + switch ($Repair.Results.$CurrentRepair) { + $true + { "SUCCESS"; break } + $false + { "FAILURE"; break } + } + @($CurrentRepair + " - " + $RepairStatus) + } + Log I 0 $Out + ### Determine Overall Repair Outcome + switch ($Agent.Health.AgentStatus) { + $SC.ApplianceStatus.A { + # Complete Repair Sequence and Exit + $Out = @( + switch ($Repair.Results) { + { @($_.$($SC.Repairs.PostRepair.Name), $_.$($SC.Repairs.Recovery.Name)) -contains $false } { + # Errors in Post-Repair/Recovery + "Some minor issues were encountered during Repairs, however the Script has found them to be resolved.", + "Possible reasons for this result may include:", + ("- A request to one or more " + $NC.Products.Agent.Name + " Services timed out"), + ("- A pre-existing operation was in place on one or more " + $NC.Products.Agent.Name + " Services") + break + } + Default { + # Agent Successfully Repaired without Issue + "The existing " + $NC.Products.Agent.Name + " installation was repaired successfully, without the need for installation." + break + } + } + ) + Log -EndSequence + Log I 0 $Out -Exit + } + Default { + $Out = @( + switch ($Script.Sequence.Status[-1]) { + $SC.SequenceStatus.C { + # Fail Repair Sequence + $Script.Sequence.Status[-1] = $SC.SequenceStatus.F + switch ($Repair.Results) { + { # Errors in Post-Repair/Recovery + @( + $_.$($SC.Repairs.PostRepair.Name), + $_.$($SC.Repairs.Recovery.Name) + ) -contains $false + } { + # Require Installation + ("One or more Post-Repair or Recovery Actions failed. The current " + $NC.Products.Agent.Name + " must be re-installed.") + break + } + Default { + # Error(s) in Standard Repair + ("Standard Repairs failed to fully correct all identified issues. The current " + $NC.Products.Agent.Name + " must be re-installed.") + break + } + } + } + $SC.SequenceStatus.E { + # Abort Repair Sequence and Require an Install + ("The Repair Sequence was aborted. The current " + $NC.Products.Agent.Name + " cannot be fixed with standard repairs and must be re-installed.") + break + } + } + ) + Log I 0 $Out + } + } + } +} + +### INSTALLATION FUNCTIONS +############################### + +function SelectInstallers { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + ### Validate Potential Installation Sources + $Install.NETLOGONAccess = + if ($Device.IsDomainJoined -eq $true) { + try + { Test-Path ("\\" + $Device.FQDN + "\NETLOGON") -PathType Container } + catch [System.IO.IOException] + { $false } + } + else { $false } + # Check each Source for Required Installers + $Install.Sources.Keys | + Sort-Object | + ForEach-Object { + $CurrentSourceName = $_ + $CurrentSource = $Install.Sources.$_ + if (($CurrentSourceName -eq "Network" -or $CurrentSourceName -eq "Sysvol") -and ($Install.NETLOGONAccess -eq $false)) { + $CurrentSource.Available = $false + $CurrentSource.AgentFound = $false + $CurrentSource.AgentValid = $false + $CurrentSource.NETFound = $false + $CurrentSource.NETValid = $false + } + else { + $CurrentSource.Available = Test-Path ($CurrentSource.Path) -PathType Container + $CurrentSource.AgentFound = + if ($CurrentSource.Available -eq $true) { + # Verify Agent Installer Exists + if (Test-Path ($CurrentSource.Path + "\" + $Config.AgentFile) -PathType Leaf) { + $true + $AgentFile = Get-Item ($CurrentSource.Path + "\" + $Config.AgentFile) 2>$null + } + else { $false } + } + else { $false } + $CurrentSource.AgentValid = + if ($CurrentSource.AgentFound -eq $true) { + # Validate Agent Installer Version + $CurrentSource.$($AgentFile.Name) = [Version] (ValidateVersion $($AgentFile.VersionInfo.FileVersion)) + if ( + (($CurrentSource.$($AgentFile.Name)) -ne $Config.AgentFileVersion) -or + (($CurrentSource.$($AgentFile.Name)) -eq "0.0.0.0") + ) + { $false } else { $true } + } + else { $false } + $CurrentSource.NETFound = + if ($CurrentSource.Available -eq $true) { + # Verify .NET Installer Exists + if (Test-Path ($CurrentSource.Path + "\" + $Config.NETFile) -PathType Leaf) { + $true + $NETFile = Get-Item ($CurrentSource.Path + "\" + $Config.NETFile) 2>$null + } + else { $false } + } + else { $false } + $CurrentSource.NETValid = + if ($CurrentSource.NETFound -eq $true) { + # Validate .NET Installer Version + $CurrentSource.$($NETFile.Name) = [Version] (ValidateVersion $($NETFile.VersionInfo.FileVersion)) + if ( + (($CurrentSource.$($NETFile.Name)) -ne $Config.NETFileVersion) -or + (($CurrentSource.$($NETFile.Name)) -eq "0.0.0.0") + ) + { $false } else { $true } + } + else { $false } + } + # Choose the Best Source for Each Installer + if (@(0, $null) -contains $Install.ChosenAgent.Count) { + # Select the Agent Source + if ($CurrentSource.AgentValid -eq $true) { + $Install.Sources.ChosenAgent = $CurrentSource.Path + $Install.ChosenAgent = @{ + "FileName" = $AgentFile.Name + "InstallPath" = @($Script.Path.InstallDrop, $AgentFile.Name) -join '\' + "Path" = @($Install.Sources.ChosenAgent, $AgentFile.Name) -join '\' + "Version" = $CurrentSource.$($AgentFile.Name) + } + } + } + if (@(0, $null) -contains $Install.ChosenNET.Count) { + # Select the .NET Source + if ($CurrentSource.NETValid -eq $true) { + $Install.Sources.ChosenNET = $CurrentSource.Path + $Install.ChosenNET = @{ + "FileName" = $NETFile.Name + "InstallPath" = @($Script.Path.InstallDrop, $NETFile.Name) -join '\' + "Path" = @($Install.Sources.ChosenNET, $NETFile.Name) -join '\' + "Version" = $CurrentSource.$($NETFile.Name) + } + } + } + } + ### Warn on Domain Joined Device with no Domain Access + if (($Device.IsDomainJoined -eq $true) -and ($Install.NETLOGONAccess -eq $false)) + { Log W 0 ("Device is joined to the [" + $Device.FQDN + "] Domain, but either cannot currently reach a Domain Controller, or does not have access to the NETLOGON Folder.") } + ### Log the Chosen Install Kits + $Install.Results.SelectedAgentKit = + switch ($Install.Sources.ChosenAgent) { + $Install.Sources.Demand.Path + { $SC.InstallKit.C; break } + $Install.Sources.Network.Path + { $SC.InstallKit.B; break } + Default + { $SC.InstallKit.A; break } + } + $Install.Results.SelectedNETKit = + switch ($Install.Sources.ChosenNET) { + $Install.Sources.Demand.Path + { $SC.InstallKit.C; break } + $Install.Sources.Network.Path + { $SC.InstallKit.B; break } + Default + { $SC.InstallKit.A; break } + } + WriteKey $Script.Results.ScriptInstallKey $Install.Results + ### Verify that Valid Installers were Found + $Available = + $Install.Sources.GetEnumerator() | + ForEach-Object { $_.Value.Available } + $AgentFound = + $Install.Sources.GetEnumerator() | + ForEach-Object { $_.Value.AgentFound } + $NETFound = + $Install.Sources.GetEnumerator() | + ForEach-Object { $_.Value.NETFound } + if (($null -ne $Install.ChosenAgent) -and ($null -ne $Install.ChosenNET)) + { Log I 0 ("Verified the correct Windows Installer versions for the " + $NC.Products.Agent.Name + " (" + $Install.ChosenAgent.Version + ") and .NET Framework (" + $Install.ChosenNET.Version + ") are available to the Script.") } + else { + # Display Required Installers + $Out = @("The following Installer(s) are required by the Script in order to perform Installation Repairs on this system:") + $Out += + ($Config.AgentFile + " Version - " + $Config.AgentVersion + " (Windows " + $Config.AgentFileVersion + ")"), + ($Config.NETFile + " Version - " + (ValidateVersion $Config.NETVersion 2) + " (Windows " + (ValidateVersion $Config.NETFileVersion 2) + ")"), + "" + if ($Available -notcontains $true) { + # No Installers Available from any Source + $Out += @("No Installation Sources were available to the Script. Make sure that the relevant , , and values in the Partner Configuration are correct and that the locations are available in the Deployment Package.") + $ExitCode = 3 + } + else { + if ( + ($AgentFound -contains $true) -and + ($NETFound -contains $true) + ) { + # Installer Version Mismatch + $Out += @("The following Installer(s) were found, but do not match the Version required by the Partner Configuration:") + switch ($Install.ChosenAgent.Version) { + { $null -ne $_ } { + # Valid Installer Found + break + } + Default { + # Collect Available Installer Paths/Versions + $Install.Sources.GetEnumerator() | + Where-Object { $_.Name -notlike "Chosen*" } | + ForEach-Object { + $AgentVersion = $_.Value.$($Config.AgentFile) + if ($null -ne $AgentVersion) { + $DisplayVersion = + if ($AgentVersion -eq "0.0.0.0") + { " - No Discovered Version" } else { (" Version - (Windows " + $AgentVersion + ")") } + $Out += @($Config.AgentFile + $DisplayVersion + " at " + $_.Value.Path) + } + } + $Values += @("", "") + break + } + } + switch ($Install.ChosenNET.Version) { + { $null -ne $_ } { + # Valid Installer Found + break + } + Default { + # Collect Available Installer Paths/Versions + $Install.Sources.GetEnumerator() | + Where-Object { $_.Name -notlike "Chosen*" } | + ForEach-Object { + $NETVersion = $_.Value.$($Config.NETFile) + if ($null -ne $NETVersion) { + $DisplayVersion = + if ($NETVersion -eq "0.0.0.0") + { " - No Discovered Version" } else { (" Version - (Windows " + $NETVersion + ")") } + $Out += @($Config.NETFile + $DisplayVersion + " at " + $_.Value.Path) + } + } + $Values += @("", "") + break + } + } + $Out += + "`nPlease update one of the following to the appropriate Version:", + "- The Installer(s) listed above at their respective locations", + "- The relevant values in the Partner Configuration:" + $Out += @($Values) + $ExitCode = 5 + } + else { + # Installer(s) Missing + $Out += @( + if (($AgentFound -notcontains $true) -and ($NETFound -notcontains $true)) + { "No Installers were found by the Script." } + else { + "The following Installer(s) were not found by the Script, or the name does not match the Partner Configuration:" + if ($AgentFound -notcontains $true) { + $Config.AgentFile + $MissingLocations += @($Install.Sources.ChosenAgent) + } + if ($NETFound -notcontains $true) { + $Config.NETFile + $MissingLocations += @($Install.Sources.ChosenNET) + } + } + ) + $Out += + "`nVerify that the Installer(s) exist at:", + $MissingLocations, + "`nAlso verify that the relevant and values in the Partner Configuration are correct." + $ExitCode = 4 + } + } + Log E $ExitCode $Out -Exit + } +} + +function GetInstallMethods { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + ### Get Potential Values for Installation + $Values = @( + $Script.ActivationKey, + $Config.ActivationKey, + $Agent.History.ActivationKey, + $Agent.Appliance.SiteID, + ($Script.CustomerID), + "$($Script.CustomerID)|$($Script.RegistrationToken)", + "$($Agent.History.ScriptSiteID)|$($Agent.History.RegistrationToken)", + "$($Config.CustomerId)|$($Config.RegistrationToken)", + $Script.CustomerID, + $Config.CustomerId + ) + #Working here + ### Populate Method Tables with Available Values + for ($i = 0; $i -lt $Values.Count; $i++) { + # Populate Method Tables + $Install.MethodData.$(AlphaValue $i) = @{ + "Attempts" = 0 + "Available" = + if ( + ($Agent.Health.AgentStatus -eq $SC.ApplianceStatus.E) -and + ($i -lt 5) + ) { + # Only use Script Customer ID for Takeover Installations + $false + } + elseif (!$Config.IsAzNableAvailable -and $SC.InstallMethods.UsesAzProxy.(AlphaValue $i)) { + # If AzNableProxy configuration isn't available and method uses it... + $false + } + elseif ($Config.IsAzNableAvailable -and $SC.InstallMethods.Type.(AlphaValue $i) -eq $SC.InstallMethods.InstallTypes.B -and $Agent.Health.Installed -eq $false) { + # If the type is AzNableProxy, it is Activation Type install and the agent is not installed + $false + } + else { $null -ne $Values[$i] -and "|" -ne $Values[$i] -and $Values[$i] -notlike "*|" } + "Failed" = $false + "FailedAttempts" = 0 + "MaxAttempts" = $SC.InstallMethods.Attempts.$(AlphaValue $i) + "Name" = $SC.InstallMethods.Names.$(AlphaValue $i) + "Parameter" = + switch ($Values[$i]) { + { $_ -match $NC.Validation.CustomerIDandToken } + { $NC.InstallParameters.B; break } + { $_ -match $NC.Validation.ActivationKey.Encoded } + { $NC.InstallParameters.A; break } + { $_ -match $NC.Validation.CustomerID } + { $NC.InstallParameters.B; break } + Default + { $null; break } + } + "Value" = $Values[$i] + "Type" = $SC.InstallMethods.Type.(AlphaValue $i) + } + # Populate Results Table only for Available Methods + if ($Install.MethodData.$(AlphaValue $i).Available -eq $true) { + $Install.MethodResults.$(AlphaValue $i) = @{ + "Method" = $SC.InstallMethods.Names.$(AlphaValue $i) + "MethodAttempts" = 0 + "MethodSuccessful" = $null + } + } + } +} + +function MethodSummary { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = "Summarizing Installation Results" + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + # Update Registry Values + $Script.Execution.AgentLastInstalled = Get-Date -UFormat $SC.DateFormat.Full + WriteKey $Script.Results.ScriptInstallKey ($Install.Results + $Install.MethodResults.$($Install.ChosenMethod.Method)) + # Log Detailed Installation Results + $Out = @("The following Installation attempts were made on the system:") + $Out += + $Install.MethodResults.GetEnumerator() | + Where-Object { $_.Value.MethodAttempts -gt 0 } | + Select-Object -ExpandProperty Name | + Sort-Object | + ForEach-Object { + $m = $_ + $MethodStatus = + switch ($Install.MethodResults.$m.MethodSuccessful) { + $true + { "SUCCESS"; break } + $false + { "FAILURE"; break } + } + $AttemptDisplayValue = + if ($Install.MethodResults.$m.MethodAttempts -eq 1) + { "Attempt" } else { "Attempts" } + @( + $Install.MethodResults.$m.Method, "-", + $Install.MethodResults.$m.MethodAttempts, $AttemptDisplayValue, "-", + $MethodStatus + ) -join ' ' + } + Log I 0 $Out +} + +function SelectInstallMethod { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + ### Fail Current Method if Limit Exceeded + if ($null -ne $Install.ChosenMethod.Method) { + $MethodData = $($Install.MethodData.$($Install.ChosenMethod.Method)) + $MethodResults = $($Install.MethodResults.$($Install.ChosenMethod.Method)) + if ($Install.ChosenMethod.FailedAttempts -ge $Install.ChosenMethod.MaxAttempts) { + # Fail the Method + $MethodData.Failed = $true + $MethodResults.MethodAttempts = $Install.ChosenMethod.Attempts + $MethodResults.MethodSuccessful = $false + } + } + ### Select the Next Install Method + for ($i = 0; $i -lt $Install.MethodData.Count; $i++) { + $MethodData = $($Install.MethodData.$(AlphaValue $i)) + if ( + ($MethodData.Available -eq $true) -and + ($MethodData.Failed -eq $false) + ) { + # Check if a Different Method was Chosen than Previously + if ($Install.ChosenMethod.Method -ne (AlphaValue $i)) { + # Initialize New Method - Reset Attempts + $Install.ChosenMethod = $MethodData + $Install.ChosenMethod.Method = AlphaValue $i + } + # Begin a New Attempt + $Install.ChosenMethod.Attempts++ + # Set Selection Flag + $MethodChosen = $true + break + } + } + if ($MethodChosen -ne $true) { + # ERROR - No Installation Methods Remaining + MethodSummary + $Out = + ("All available Methods and Attempts to install the " + $NC.Products.Agent.Name + " were unsuccessful.`n"), + ("Review the Event Log for any entries made by the " + $NC.Products.Agent.InstallerName + " Event Source for more details.") + Log E 12 $Out -Exit + } +} + +function UpdateHistory { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + # Update Configuration History File + # This section is also responsible for: + # "Activation Key : Token / Appliance ID (Historical Installation)" + $LastUpdate = + if ($Agent.History.Count -gt 0) + { $Agent.History } else { @{} } + $Agent.Appliance.GetEnumerator() | + ForEach-Object { + if ($null -ne $_.Value) + { $LastUpdate.$($_.Name) = $_.Value } + } + $LastUpdate.HistoryUpdated = Get-Date -UFormat $SC.DateFormat.Full + # Retain Customer IDs that were Successful in Activating the Agent + if ( + ($null -ne $Script.CustomerID) -and + ($Install.ChosenMethod.Value -eq $Script.CustomerID) + ) + { $LastUpdate.ScriptSiteID = $Script.CustomerID } + WriteXML $Agent.Path.History "Config" $LastUpdate +} + +function CheckMSIService { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + # Check if an Installation is in Progress + for ($i = 0; $i -lt ($Config.InstallTimeoutPeriod * 6); $i++) { + $MSI_IP = ((Get-Process -Name "msiexec" -ErrorAction SilentlyContinue).Count -gt 1) + if ($MSI_IP -eq $false) + { break } else { Start-Sleep 10 } + } + # Update Optional Counter if Timeout is Reached + if ($i -ge ($Config.InstallTimeoutPeriod * 6)) { + # Exit - Windows Installer Service Unavailable + $Out = ( + "The Windows Installer Service has been unavailable for the timeout period of " + + $Config.InstallTimeoutPeriod + " minutes.`n`n" + + "This could be due to an Installer that is requesting user input to continue. " + ) + $Out += + switch ($Script.Execution.ScriptMode) { + $SC.ExecutionMode.A + { "Run the Script again to re-attempt installation."; break } + $SC.ExecutionMode.B + { "Installation will be re-attempted at the next Device boot."; break } + } + $Out += " If the problem persists, consider rebooting the Device to clear any pending install operations." + Log E 9 $Out + } +} + +function InstallNET { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = "Installing Prerequisite .NET Framework" + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + ### Retrieve the Chosen Installer for Local Install + # Remove an Old Installer if it Exists + ValidateItem $Install.ChosenNET.InstallPath -RemoveItem >$null + # Transfer the Installer + try + { Copy-Item $Install.ChosenNET.Path $Install.ChosenNET.InstallPath -Force 2>&1 -ErrorAction Stop } + catch { + # ERROR - File Transfer Failed + $ExceptionInfo = $_.Exception + $InvocationInfo = $_.InvocationInfo + CatchError 102 -Exit + } + # .NET Framework Install Properties + $NET = New-Object System.Diagnostics.ProcessStartInfo ($env:windir + "\system32\cmd.exe") + $NET.UseShellExecute = $false + $NET.CreateNoWindow = $true + $NET.Arguments = ('/C "' + $Install.ChosenNET.InstallPath + '" /q /norestart') + # Check Availability of Windows Installer + CheckMSIService + # Install .NET Framework + [System.Diagnostics.Process]::Start($NET).WaitForExit() >$null + # Re-Check .NET Framework Version + GetNETVersion + $Out = ( + ".NET Framework Version " + (ValidateVersion $Config.NETVersion 2) + + " is required prior to installation of " + $NC.Products.Agent.Name + + " Version " + $Config.AgentVersion + ) + if ($Device.NETProduct -lt (ValidateVersion $Config.NETVersion 2)) { + # Exit - .NET Framework Installation Failed + $Out += ". An error occurred during installation.`n`nReview the Event Log for relevant details." + Log E 10 $Out -Exit + } + $Out += " and was installed successfully." + Log I 0 $Out +} + +function RemoveAgent { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + ### Function Body + ############################### + # Agent Removal Properties + $REM = New-Object System.Diagnostics.ProcessStartInfo ($env:windir + "\system32\cmd.exe") + $REM.UseShellExecute = $false + $REM.CreateNoWindow = $true + $REM.Arguments = ('/C "' + $Agent.Registry.UninstallString + ' /QN /NORESTART"') + # Check Availability of Windows Installer + CheckMSIService + # Remove the Existing Agent + FixServices -Disable + [System.Diagnostics.Process]::Start($REM).WaitForExit() >$null + # Verify Removal was Successful + DiagnoseAgent -NoLog -NoServerCheck + if ($Agent.Health.Installed -eq $true) { + + #If the forced removal of the agent is enabled + if ($Config.ForceAgentCleanup) { + $FAC = New-Object System.Diagnostics.ProcessStartInfo ($env:windir + "\system32\cmd.exe") + $FAC.UseShellExecute = $false + $FAC.CreateNoWindow = $true + $FAC.Arguments = ('/C "' + $Script.Path.AgentCleanup + '"') + # Run the forced cleanup + [System.Diagnostics.Process]::Start($FAC).WaitForExit() >$null + + # Verify Removal was Successful again + DiagnoseAgent -NoLog -NoServerCheck + + if ($Agent.Health.Installed -eq $true) { + # Exit - Agent Removal Failed + FixServices -Restart + $Out = ( + "Forced and MSI Removal of the existing " + $NC.Products.Agent.Name + " failed. " + + "Manual forcible removal is required for the Script to continue." + ) + Log E 11 $Out -Exit + } + } + else { + # Exit - Agent Removal Failed + FixServices -Restart + $Out = ( + "MSI Removal of the existing " + $NC.Products.Agent.Name + " failed. " + + "Manual forcible removal is required for the Script to continue." + ) + Log E 11 $Out -Exit + } + # If forced removal successful, flag existing agent removal as true + $Install.ExistingAgentRemoved = $true + } + else + { $Install.ExistingAgentRemoved = $true } +} + +function VerifyPrerequisites { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = "Verifying Installation Requirements" + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + ### Check Connectivity to Partner Server + if ($Install.NCServerAccess -eq $false) { + # Exit - Installer will Fail to Authenticate with Server + $Out = + ("The Device is currently unable to reliably reach the " + $NC.Products.NCServer.Name + ". Installation attempts will fail authentication.`n"), + "This may be caused by lack of Internet connectivity, a poor connection, or DNS is unavailable or unable to resolve the address.", + "If this issue persists, verify the value in the Partner Configuration is correct." + Log E 6 $Out -Exit + } + ### Validate and Choose from Available Installers + SelectInstallers + ### Check if the Script has Sufficient Info to Install the Agent + GetInstallMethods + ### Before proceeding, run GetCustomInstallMethods, if a custom module is loaded it will override the empty function + GetCustomInstallMethods + # Verify at least one Method is Available for Installation + $MethodsAvailable = ( + $Install.MethodData.Keys | + ForEach-Object { $Install.MethodData.$_.Available } + ) -contains $true + if ($MethodsAvailable -ne $true) { + # Exit - No Available Installation Methods + $Out = + if ($null -eq $CustomerID) { + @("An " + $NC.Products.Agent.IDName + " was not provided to the Script and is required for Installation.`n") + $ExitCode = 7 + } + else { + @("The " + $NC.Products.Agent.IDName + " provided to the Script [" + $CustomerID + "] is invalid. A valid Customer ID is required for Installation.`n") + $ExitCode = 8 + } + $Out += + switch ($Script.Execution.ScriptMode) { + $SC.ExecutionMode.A + { @("For On-Demand Deployment - Please specify a valid " + $NC.Products.Agent.IDName + " when running " + $SC.Names.LauncherFile + "."); break } + $SC.ExecutionMode.B + { @("For Group Policy Deployment - Verify the " + $NC.Products.Agent.IDName + " is free of typographical errors, and is the only item present in the Parameters field for the GPO."); break } + } + Log E $ExitCode $Out -Exit + } + ### Verify the Required Version of .NET Framework is Installed + # Verify the Working Install Folder + $DropFolderExists = ValidateItem $Script.Path.InstallDrop -Folder + if ($DropFolderExists -eq $false) { + # ERROR - Transfer Folder Creation Failed + Quit 104 + } + # Get the Currently Installed Product/Version + GetNETVersion + # Install .NET Framework if Required + if ($Device.NETProduct -lt (ValidateVersion $Config.NETVersion 2)) + { InstallNET } + ### Determine Required Installation Action + $Install.RequiredAction = + if ($Agent.Health.Installed -eq $true) { + if (($Agent.Health.VersionCorrect -eq $true) -or ($Agent.Health.AssignedToPartnerServer -eq $false)) + { $SC.InstallActions.C } else { $SC.InstallActions.B } + } + else { $SC.InstallActions.A } +} + +function RequestAzWebProxyToken { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = "Requesting AzWebproxyToken" + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + ### Attempt to connect to the AzNableProxy + $Response = $null + $Uri = "https://$($Config.AzNableProxyUri)/api/Get?Code=$($Config.AzNableAuthCode)&ID=" + try { + $Uri += "$($Install.ChosenMethod.Value)" + $Response = (Invoke-WebRequest -Method GET -Uri $Uri -UseBasicParsing).Content + } + catch { + $Out = "Error retrieving token from $Uri using $($Install.ChosenMethod.Name)" + Log E 15 $Out + } + + ### Validate that the response is a GUID + $Install.ChosenMethod.Token = if ($Response -match $SC.Validation.GUID) { $Response } else { $null } + + ### If the method is an Activation Key, populate the value correctly + if ($null -ne $Install.ChosenMethod.Token -and $Install.ChosenMethod.Type -eq $SC.InstallMethods.InstallTypes.B) { + $Install.ChosenMethod.Value = NewEncodedKey $Agent.Appliance.AssignedServer $Agent.Appliance.ID $Install.ChosenMethod.Token + } + else { + $Install.ChosenMethod.Value = "$($Install.ChosenMethod.Value)|$($Install.ChosenMethod.Token)" + } + +} + +function InstallAgent { + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = + switch ($Install.RequiredAction) { + $SC.InstallActions.A + { "Installing New Agent"; break } + $SC.InstallActions.B + { "Upgrading Existing Agent Installation"; break } + $SC.InstallActions.C + { "Replacing Existing Agent Installation"; break } + } + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + ### Perform WSDL verfication before attempting any install or removal + if ($Config.UseWSDLVerifcation) { + $client = New-Object System.Net.WebClient + try { + $response = $client.DownloadString("https://$($Config.NCServerAddress)/dms2/services2/ServerEI2?wsdl") + $xmlResponse = [xml]$response + if ($xmlResponse.definitions.service.port.address.location -eq "https://$($Config.NCServerAddress)/dms2/services2/ServerEI2") { + $Flag = "I" + $Out = ("WSDL verification succeeded, proceeding with installation actions.") + Log $Flag 0 $Out + } + else { + $Flag = "E" + $Out = ("WSDL verification failed. Expected: https://$($Config.NCServerAddress)/dms2/services2/ServerEI2 Received:$($xmlResponse.definitions.service.port.address.location)") + Log $Flag 13 $Out + } + } + catch { + $Flag = "E" + $Out = ("WSDL verification method for " + $NC.Products.NCServer.Name + "failed prior to install. Terminating install.") + Log $Flag 13 $Out -Exit + } + } + + ### Attempt Agent Installation + for ( + $Install.ChosenMethod.FailedAttempts = 0 + $true # SelectInstallMethod Function acts as the Loop Condition + $Install.ChosenMethod.FailedAttempts++ + ) { + ### Choose the Best Install Method + SelectInstallMethod + ### Build the Install String + + # Activation Key methods + if ($Install.ChosenMethod.Type -eq $SC.InstallMethods.InstallTypes.A) { + $Install.AgentString = "/S /V`" /qn AGENTACTIVATIONKEY=$($Install.ChosenMethod.Value)" + } + elseif ($Install.ChosenMethod.Type -eq $SC.InstallMethods.InstallTypes.B) { + ### Request a registration token using AzNableProxy + RequestAzWebProxyToken + $Install.AgentString = "/S /V`" /qn AGENTACTIVATIONKEY=$($Install.ChosenMethod.Value)" + } + + ### Populate customer ID/String method + else { + $Install.AgentString = @( + '/S /V" /qn', + #Server address + (@($NC.InstallParameters.D, $Config.NCServerAddress) -join '='), + #Port + (@($NC.InstallParameters.E, "443") -join '='), + #Protocol + (@($NC.InstallParameters.F, "HTTPS") -join '=') + ) -join ' ' + + # Gather the token from the AzWebproxy service if appropriate + if ($Install.ChosenMethod.Type -eq $SC.InstallMethods.InstallTypes.D) { + RequestAzWebProxyToken + } + $CustomerIDParam = $Install.ChosenMethod.Value.Split('|')[0] + $TokenParam = $Install.ChosenMethod.Value.Split('|')[1] + $Install.AgentString += (' ' + (@($NC.InstallParameters.B, $CustomerIDParam) -join '=')) + $Install.AgentString += (' ' + (@($NC.InstallParameters.H, $TokenParam) -join '=')) + # Customer specific flag + $Install.AgentString += (' ' + (@($NC.InstallParameters.C, "1") -join '=')) + # Add Proxy String if it Exists + if ($null -ne $Config.ProxyString) + { $Install.AgentString += (' ' + (@($NC.InstallParameters.G, $Config.ProxyString) -join '=')) } + # Complete the String + + } + + # Enclose install string with final quotation mark + $Install.AgentString += '"' + + Log I 0 $Install.AgentString + ### Set Agent Install Properties + $INST = New-Object System.Diagnostics.ProcessStartInfo ($Install.ChosenAgent.InstallPath) + $INST.UseShellExecute = $false + $INST.CreateNoWindow = $true + $INST.Arguments = $Install.AgentString + ### Perform Required Installation Actions + # Ensure MMC is not currently in use on the System (Prevents Service Deletion) + foreach ($p in @("mmc", "taskmgr")) + { Get-Process -Name $p 2>$null | Stop-Process -Force 2>$null } + # Remove the Existing Agent if Required + if ( + (@($SC.InstallActions.B, $SC.InstallActions.C) -contains $Install.RequiredAction) -and + ($Agent.Health.Installed -eq $true) -and + ($Install.ExistingAgentRemoved -ne $true) + ) + { RemoveAgent } + ### Retrieve the Chosen Installer for Local Install + # Remove an Old Installer if it Exists + ValidateItem $Install.ChosenAgent.InstallPath -RemoveItem >$null + # Transfer the Installer + try + { Copy-Item $Install.ChosenAgent.Path $Install.ChosenAgent.InstallPath -Force 2>&1 -ErrorAction Stop } + catch { + $ExceptionInfo = $_.Exception + $InvocationInfo = $_.InvocationInfo + CatchError 102 -Exit + } + # Check Availability of Windows Installer + CheckMSIService + # Install the Required Agent + $Proc = [System.Diagnostics.Process]::Start($INST) + $Proc.WaitForExit() + $Install.Results.InstallExitCode = $Proc.ExitCode + ### Verify the Installation Status + if ($Install.Results.InstallExitCode -eq 0) { + # Wait for the Agent Services to Start + for ( + $i = 0 + ( + ($i -lt 12) -and + ( + ($Agent.Health.ServicesExist -eq $false) -or + ($Agent.Health.ServicesRunning -eq $false) + ) + ) + $i++ + ) + { QueryServices; Start-Sleep 10 } + # Run a Service Repair if the Agent Services Haven't Started After Install + if ($i -ge 12) + { $ServicesStarted = FixServices } + if ( + (($i -ge 12) -and ($ServicesStarted -eq $true)) -or + ($i -lt 12) + ) { + # Verify Services are Running Post-Install + $Services = VerifyServices + # Enforce Service Startup Type + $Startup = FixStartupType + # Enforce Service Behavior + $Behavior = FixFailureBehavior + $Install.Results.VerifiedServices = ($Services -eq $true) -and ($Startup -eq $true) -and ($Behavior -eq $true) + # Re-Check Agent Health Post-Install + DiagnoseAgent + if (($Agent.Health.Installed -eq $true) -and ($Agent.Health.VersionCorrect -eq $true)) { + $Install.Results.VerifiedStatus = $true + $Install.MethodResults.$($Install.ChosenMethod.Method).MethodAttempts = $Install.ChosenMethod.Attempts + $Install.MethodResults.$($Install.ChosenMethod.Method).MethodSuccessful = $true + break + } + } + } + } + ### Summarize Installation Results + # Update Historical Configuration + UpdateHistory + # Summarize Methods/Attempts Taken + MethodSummary + ### Report Overall Installation Status + if ($Agent.Health.AgentStatus -ne $SC.ApplianceStatus.A) { + # Warn that Repairs May still be Needed + $Out = @( + $NC.Products.Agent.Name, "Version", $Config.AgentVersion, + "was installed successfully, however, some items may still require Repair as indicated above. " + ) -join ' ' + $Out += + switch ($Script.Execution.ScriptMode) { + $SC.ExecutionMode.A + { "Run the Script again to resolve these items."; break } + $SC.ExecutionMode.B + { "These items will be resolved at the next Device boot."; break } + } + Log W 0 $Out + } + else { + # Installation was a Complete Success + $Out = @( + $NC.Products.Agent.Name, "Version", $Config.AgentVersion, + "was installed successfully! No outstanding issues were detected with the new installation." + ) -join ' ' + Log I 0 $Out + } +} \ No newline at end of file diff --git a/Agent/PartnerConfig.xml b/Agent/PartnerConfig.xml new file mode 100644 index 0000000..2328a59 --- /dev/null +++ b/Agent/PartnerConfig.xml @@ -0,0 +1,122 @@ + + + + 6.0.1 + + + +My MSP +(888) MSP-4ALL +help@mymsp.com + + + + + 5 + + 5 + + True + + True + + + + + + 20 + + 20 + + + + + + + False + + RESTART + RESTART + RESTART + + + 120 + 120 + 120 + + + + + 1440 + + Auto + + + + C:\AGENT + + AGENT + + + CurrentAgent + + NET4_5_2-Universal.exe + + 4.5.2 + + 4.5.51209.34209 + + WindowsAgentSetup.exe + + 2020.1.5.425 + + 2020.1.50425.0 + + + + + + + + + + \ No newline at end of file diff --git a/Custom Library/CustomOverrideExample.psm1 b/Custom Library/CustomOverrideExample.psm1 new file mode 100644 index 0000000..b8a3b20 --- /dev/null +++ b/Custom Library/CustomOverrideExample.psm1 @@ -0,0 +1,177 @@ +function RequestAzWebProxyToken { + <# + Example function override for RequestAzWebProxyToken + + The the Azure function we can take more than one parameter in the GET function. + while the $Install.ChosenMethod.Value contains the one parameter used to retrieve the token + the rest of the parameters passed can be taken and added inserted to a table inside the function + that data from the table can be later used to generate a HTML report + or opened in an Excel spreadsheet etc. + + #> + + ### Definitions + ############################### + # Function Info + $Function.LineNumber = $MyInvocation.ScriptLineNumber + $Function.Name = '{0}' -f $MyInvocation.MyCommand + # Execution Info + $Script.Execution.ScriptAction = "Requesting AzWebproxyToken" + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Function Body + ############################### + ### Attempt to connect to the AzNableProxy + $Response = $null + $Uri = "https://$($Config.AzNableProxyUri)/api/Get?Code=$($Config.AzNableAuthCode)&ID=" + try { + $Uri += "$($Install.ChosenMethod.Value)" + + ### ... And here is the change ... + ### ||||||||||||||||||||||||||||||| + ### VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV + ### We add our own custom params to get custom data on devices using the AzNableProxy service + $Uri += "&HostName=$($device.HostName)&OSName=$($device.OSName)" + $Uri += "&ApplianceID=$($agent.Appliance.ID)" + $Response = Invoke-RestMethod -Method GET -Uri $Uri + } + catch { + $Out = "Error retrieving token from $Uri using $($Install.ChosenMethod.Name)" + Log E 15 $Out + } + + ### Validate that the response is a GUID + $Install.ChosenMethod.Token = if ($Response -match $SC.Validation.GUID) { $Response } else { $null } + + ### If the method is an Activation Key, populate the value correctly + if ($null -ne $Install.ChosenMethod.Token -and $Install.ChosenMethod.Type -eq $SC.InstallMethods.InstallTypes.B) { + $Install.ChosenMethod.Value = NewEncodedKey $Agent.Appliance.AssignedServer $Agent.Appliance.ID $Install.ChosenMethod.Token + } + else { + $Install.ChosenMethod.Value = "$($Install.ChosenMethod.Value)|$($Install.ChosenMethod.Token)" + } + +} +<# This Quit function is copied from the orginal InstallAgent-Core.psm1 but modifed slightly + By declaring the function in a module loaded after the original one, the +#> +function Quit { + ### Parameters + ############################### + param ($Code) + ### Function Body + ############################### + ### Update the Script Registry Keys + # Assign the Appropriate Error Message + switch ($Code) { + # Successful Execution + 0 { + $Script.Execution.ScriptAction = $SC.SuccessScriptAction + $LCode = AlphaValue $Code + break + } + # Documented Typical Error + { @(1..25) -contains $_ } + { $LCode = AlphaValue $Code; break } + # Documented Internal Error + { @(100..125) -contains $_ } + { $LCode = AlphaValue $Code -2Digit; break } + # Undocumented Error + Default + { $Code = 999; $LCode = "Error"; break } + } + $Comment = $SC.Exit.$LCode.ExitResult + ### Publish Execution Results to the Registry + # Create a New Registry Key for Script Results and Actions + if ((Test-Path ($Script.Results.ScriptKey)) -eq $false) + { New-Item $($Script.Results.ScriptKey) -Force >$null } + # Collect Final Execution Values + $Script.Execution.ScriptResult = $Comment + $Script.Execution.ScriptExitCode = $Code + # Write Final Execution Values + WriteKey $Script.Results.ScriptKey $Script.Execution + ### Append the Function Info if a Validation Error Occurs + if ($Code -match $SC.Validation.InternalErrorCode) { + $Script.Results.Function = $Function.Name + $Script.Results.LineNumber = $Function.LineNumber + $Script.Results.Details += @("`Name== Error Details ==") + $Script.Results.Details += @($SC.Exit.$LCode.ExitMessage) + $Script.Results.Details += + if ($null -ne $Script.Results.Function) + { @("Function Name: " + $Script.Results.Function) } + $Script.Results.Details += + if ($null -ne $Script.Results.LineNumber) + { @("Called at Line: " + $Script.Results.LineNumber) } + $Script.Results.Details += + if ($null -ne $Script.Results.Parameter) + { @("Parameter Name: " + $Script.Results.Parameter) } + $Script.Results.Details += + if ($null -ne $Script.Results.GivenParameter) + { @("Given Parameter: " + $Script.Results.GivenParameter) } + } + ### Format the Message Data for the Event Log + # Add the Overall Script Result + $Script.Results.EventMessage += @("Overall Script Result: " + $SC.Exit.$LCode.ExitType + "`Name") + # Add the Completion Status of Each Sequence + $Script.Sequence.Order | + ForEach-Object { + $Script.Results.EventMessage += @( + $_, + $Script.Sequence.Status[([Array]::IndexOf($Script.Sequence.Order, $_))] + ) -join ' - ' + } + # Add the Detailed Messages for Each Sequence + $Script.Results.EventMessage += @("`Name" + ($Script.Results.Details -join "`Name")) + # For Typical Errors, Add the Branded Error Contact Message from Partner Configuration + if ( + ($Code -ne 999) -and + ($Code -ne 0) -and + ($null -ne $Config.ErrorContactInfo) + ) { + $Script.Results.EventMessage += + "`Name--=========================--", + "`nTo report this documented issue, please submit this Event Log entry to:`Name", + $Config.ErrorContactInfo + } + # Combine All Message Items + $Script.Results.EventMessage = ($Script.Results.EventMessage -join "`Name").TrimEnd('') + ### Publish Execution Results to the Event Log + # Create a New Key for the Event Source if Required + if ((Test-Path $Script.Results.ScriptEventKey) -eq $false) + { New-EventLog -Source $Script.Results.ScriptSource -LogName $Script.Results.EventLog } + # Write the Event + Write-EventLog -LogName $Script.Results.EventLog -Source $Script.Results.ScriptSource -EventID (9000 + $Code) -EntryType $Script.Results.ErrorLevel -Message $Script.Results.EventMessage -Category 0 + ### Cleanup Outdated Items + $SC.Paths.Old.Values | + ForEach-Object { Remove-Item $_ -Recurse -Force 2>$null } + ### Cleanup Working Folder + Remove-Item $Script.Path.InstallDrop -Force -Recurse 2>$null + if (!$DebugMode.isPresent) { + Remove-Item $Script.Path.TempFolder -Force -Recurse 2>$null + } + + ### ... And here is the change ... + ### ||||||||||||||||||||||||||||||| + ### VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV + ### We gather together important variables and results into a hashtable + + $POST = @{} + $POST.Appliance = $Agent.Appliance + $POST.EventMsg = $Script.Results.EventMessage + $POST.$Config = $Config + $POST.InstallMethodData = $Install.MethodData + $POST.ResultCode = $Code + + # We convert it to JSON + $POST = $POST | ConvertTo-Json -Depth 5 -Compress + + # Assemble the URL + $Uri = "https://$($Config.AzNableProxyUri)/api/Post?Code=$($Config.AzNableAuthCode)" + try { + # Then POST the JSON body to our Azure service. + Invoke-RestMethod -Method POST -Uri $Uri -Body $POST + } catch { + Write-Host "Error uploading telemetry to Azure" + } + + exit $Code +} \ No newline at end of file diff --git a/Custom Library/GetCustomInstallMethodExamples.psm1 b/Custom Library/GetCustomInstallMethodExamples.psm1 new file mode 100644 index 0000000..770f732 --- /dev/null +++ b/Custom Library/GetCustomInstallMethodExamples.psm1 @@ -0,0 +1,55 @@ +### Create example Customer/Token table for example +$CustomerTokenTable = @{} +100..110 | % { $CustomerTokenTable[[string]$_] = [System.Guid]::NewGuid() } + +### Template for agent activation key install method data +$ActivationKeyTemplate = @{ + "Parameter" = "AGENTACTIVATIONKEY" + "FailedAttempts" = 0 + "Type" = "Activation Key: Token/AppId" + "MaxAttempts" = 1 + "Value" = "" + "Name" = "Activation Key : Token (Current Script) / Appliance ID (Existing Installation)" + "Attempts" = 0 + "Failed" = $false + "Available" = $true +} + +### Template for registration key install method data +$RegistrationKeyTemplate = @{ + "Parameter" = "CUSTOMERID" + "FailedAttempts" = 0 + "Type" = "Registration Token: CustomerId/Token" + "MaxAttempts" = 1 + "Value" = "|" + "Name" = "Site ID/Registration Token (Current Script)" + "Attempts" = 0 + "Failed" = $false + "Available" = $true +} + +function GetCustomInstallMethods { + # Check if the script input is not null + if ($null -ne $Script.CustomerID) { + # and the CustomerID is in our custom lookup table.. + if ($null -ne $CustomerTokenTable[[string]$Script.CustomerID]) { + # Construction the RegistrationKey type activation used for replace/new install methods + $NewMethodData = $RegistrationKeyTemplate.Clone() + # Construct the value, for our purposes we use the | character as a delimeter between the ID and token + $NewMethodData.Value = "$($Script.CustomerID)|$($CustomerTokenTable[[string]$Script.CustomerID])" + # Overwrite the Method data for Method F, which is the CutomerID/Reg token from current script + $Install.MethodData.F = $NewMethodData + + # We can also generate an activation key for upgrades + if ($null -ne $Agent.Appliance.ID) { + # Construction the RegistrationKey type activation used for replace/new install methods + $NewActKeyMethodData = $ActivationKeyTemplate.Clone() + # Insert the activation key value + $NewActKeyMethodData.Value = NewEncodedKey -Server $Agent.Appliance.AssignedServer -ID $Agent.Appliance.ID -token ($CustomerTokenTable[[string]$Script.CustomerID]).Guid + # Override the Method data for Method A + $Install.MethodData.A = $NewActKeyMethodData + + } + } + } +} diff --git a/Custom Service Package/Agent Installer v6.amp b/Custom Service Package/Agent Installer v6.amp new file mode 100644 index 0000000..62a5e36 --- /dev/null +++ b/Custom Service Package/Agent Installer v6.amp @@ -0,0 +1,140 @@ + + + + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.ScriptAction + OutputObject + + ScriptAction + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.ScriptExitCode + OutputObject + + ScriptExitCode + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.ScriptLastRan + OutputObject + + ScriptLastRan + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.ScriptMode + OutputObject + + ScriptMode + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.ScriptResult + OutputObject + + ScriptResult + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.ScriptSequence + OutputObject + + ScriptSequence + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.AgentLastDiagnosed + OutputObject + + AgentLastDiagnosed + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.AgentLastInstalled + OutputObject + + AgentLastInstalled + + + 14811824-0d40-43b6-8f73-e74d6e3706c9.ScriptVersion + OutputObject + + ScriptVersion + + + + + + + + + 504,745 + Assembly references and imported namespaces serialized as XML namespaces + + + + + + + + + + [RunPowerShellScript_ScriptAction] + + + + + [RunPowerShellScript_ScriptExitCode] + + + + + [RunPowerShellScript_ScriptLastRan] + + + + + [RunPowerShellScript_ScriptMode] + + + + + [RunPowerShellScript_ScriptResult] + + + + + [RunPowerShellScript_ScriptSequence] + + + + + [RunPowerShellScript_ScriptVersion] + + + + + [RunPowerShellScript_AgentLastDiagnosed] + + + + + [RunPowerShellScript_AgentLastInstalled] + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Custom Service Package/Agent Installer.xml b/Custom Service Package/Agent Installer.xml new file mode 100644 index 0000000..da4d5de --- /dev/null +++ b/Custom Service Package/Agent Installer.xml @@ -0,0 +1,159 @@ + + + + + + + + Version 6.0.0 of the Agent Installer Status for the new PowerShell based deployment package / InstallAgent + Agent Installer + Agent Installer + Agent Installer + Agent Installer + + + + + + + + + + + Scan Interval + Scan Interval + The number of minutes between scans. + + + Automation Manager Policy + Automation Manager Policy + + + + Service Identifier + Service Identifier + + + + + + + + 3495500 + 3495501 + 3495502 + 3495503 + 3495504 + 3495505 + 3495506 + 3495507 + 3495508 + + + + + + + + + + + Script Action + SD390364_ScriptAction + + + + + + + + + + Script Exit Code + SD390364_ScriptExitCode + + + + + + + + + + Script Last Ran + SD390364_ScriptLastRan + + + + + + + + + + Script Mode + SD390364_ScriptMode + + + + + + + + + + Script Result + SD390364_ScriptResult + + + + + + + + + + Script Sequence + SD390364_ScriptSequence + + + + + + + + + + Script Version + SD390364_ScriptVersion + + + + + + + + + + Agent Last Diagnosed + SD390364_AgentLastDiagnosed + + + + + + + + + + Agent Last Installed + SD390364_AgentLastInstalled + + + + + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48UG9saWN5IERlc2NyaXB0aW9uPSJWbVZ5YzJsdmJpQTJMakF1TUNCdlppQjBhR1VnUVdkbGJuUWdTVzV6ZEdGc2JHVnlJRk4wWVhSMWN5Qm1iM0lnZEdobElHNWxkeUJRYjNkbGNsTm9aV3hzSUdKaGMyVmtJR1JsY0d4dmVXMWxiblFnY0dGamEyRm5aU0F2SUVsdWMzUmhiR3hCWjJWdWRBPT0iIEV4ZWN1dGlvblR5cGU9IkxvY2FsIiBJRD0iODhmOWJjZTUtMzMyZS00MzYzLTg3MzYtN2Y4MzcwY2RjNjU3IiBNaW5pbXVtUFNWZXJzaW9uUmVxdWlyZWQ9IjAuMC4wIiBOYW1lPSJBZ2VudCBJbnN0YWxsZXIgdjYiIFJlbW90ZUNhdGVnb3J5PSIwIiBWZXJzaW9uPSIyLjEwLjAuMTkiPgogIDxPYmplY3QgRGF0YT0iJmx0O3htbCZndDsmbHQ7T3V0cHV0UGFyYW1ldGVycyZndDsmbHQ7UGFyYW1ldGVyIFBhcmFtZXRlck5hbWU9JnF1b3Q7U2NyaXB0QWN0aW9uJnF1b3Q7IExhYmVsPSZxdW90O1NjcmlwdCBBY3Rpb24mcXVvdDsgUGFyYW1ldGVyVHlwZT0mcXVvdDtzdHJpbmcmcXVvdDsgVmFsdWU9JnF1b3Q7UnVuIFBvd2VyU2hlbGwgU2NyaXB0LlNjcmlwdCBBY3Rpb24mcXVvdDsgLyZndDsmbHQ7UGFyYW1ldGVyIFBhcmFtZXRlck5hbWU9JnF1b3Q7U2NyaXB0RXhpdENvZGUmcXVvdDsgTGFiZWw9JnF1b3Q7U2NyaXB0IEV4aXQgQ29kZSZxdW90OyBQYXJhbWV0ZXJUeXBlPSZxdW90O251bWJlciZxdW90OyBWYWx1ZT0mcXVvdDtSdW4gUG93ZXJTaGVsbCBTY3JpcHQuU2NyaXB0IEV4aXQgQ29kZSZxdW90OyAvJmd0OyZsdDtQYXJhbWV0ZXIgUGFyYW1ldGVyTmFtZT0mcXVvdDtTY3JpcHRMYXN0UmFuJnF1b3Q7IExhYmVsPSZxdW90O1NjcmlwdCBMYXN0IFJhbiZxdW90OyBQYXJhbWV0ZXJUeXBlPSZxdW90O2RhdGUmcXVvdDsgVmFsdWU9JnF1b3Q7UnVuIFBvd2VyU2hlbGwgU2NyaXB0LlNjcmlwdCBMYXN0IFJhbiZxdW90OyAvJmd0OyZsdDtQYXJhbWV0ZXIgUGFyYW1ldGVyTmFtZT0mcXVvdDtTY3JpcHRNb2RlJnF1b3Q7IExhYmVsPSZxdW90O1NjcmlwdCBNb2RlJnF1b3Q7IFBhcmFtZXRlclR5cGU9JnF1b3Q7c3RyaW5nJnF1b3Q7IFZhbHVlPSZxdW90O1J1biBQb3dlclNoZWxsIFNjcmlwdC5TY3JpcHQgTW9kZSZxdW90OyAvJmd0OyZsdDtQYXJhbWV0ZXIgUGFyYW1ldGVyTmFtZT0mcXVvdDtTY3JpcHRSZXN1bHQmcXVvdDsgTGFiZWw9JnF1b3Q7U2NyaXB0IFJlc3VsdCZxdW90OyBQYXJhbWV0ZXJUeXBlPSZxdW90O3N0cmluZyZxdW90OyBWYWx1ZT0mcXVvdDtSdW4gUG93ZXJTaGVsbCBTY3JpcHQuU2NyaXB0IFJlc3VsdCZxdW90OyAvJmd0OyZsdDtQYXJhbWV0ZXIgUGFyYW1ldGVyTmFtZT0mcXVvdDtTY3JpcHRTZXF1ZW5jZSZxdW90OyBMYWJlbD0mcXVvdDtTY3JpcHQgU2VxdWVuY2UmcXVvdDsgUGFyYW1ldGVyVHlwZT0mcXVvdDtzdHJpbmcmcXVvdDsgVmFsdWU9JnF1b3Q7UnVuIFBvd2VyU2hlbGwgU2NyaXB0LlNjcmlwdCBTZXF1ZW5jZSZxdW90OyAvJmd0OyZsdDtQYXJhbWV0ZXIgUGFyYW1ldGVyTmFtZT0mcXVvdDtTY3JpcHRWZXJzaW9uJnF1b3Q7IExhYmVsPSZxdW90O1NjcmlwdCBWZXJzaW9uJnF1b3Q7IFBhcmFtZXRlclR5cGU9JnF1b3Q7bnVtYmVyJnF1b3Q7IFZhbHVlPSZxdW90O1J1biBQb3dlclNoZWxsIFNjcmlwdC5TY3JpcHQgVmVyc2lvbiZxdW90OyAvJmd0OyZsdDtQYXJhbWV0ZXIgUGFyYW1ldGVyTmFtZT0mcXVvdDtBZ2VudExhc3REaWFnbm9zZWQmcXVvdDsgTGFiZWw9JnF1b3Q7QWdlbnQgTGFzdCBEaWFnbm9zZWQmcXVvdDsgUGFyYW1ldGVyVHlwZT0mcXVvdDtkYXRlJnF1b3Q7IFZhbHVlPSZxdW90O1J1biBQb3dlclNoZWxsIFNjcmlwdC5BZ2VudCBMYXN0IERpYWdub3NlZCZxdW90OyAvJmd0OyZsdDtQYXJhbWV0ZXIgUGFyYW1ldGVyTmFtZT0mcXVvdDtBZ2VudExhc3RJbnN0YWxsZWQmcXVvdDsgTGFiZWw9JnF1b3Q7QWdlbnQgTGFzdCBJbnN0YWxsZWQmcXVvdDsgUGFyYW1ldGVyVHlwZT0mcXVvdDtkYXRlJnF1b3Q7IFZhbHVlPSZxdW90O1J1biBQb3dlclNoZWxsIFNjcmlwdC5BZ2VudCBMYXN0IEluc3RhbGxlZCZxdW90OyAvJmd0OyZsdDsvT3V0cHV0UGFyYW1ldGVycyZndDsmbHQ7L3htbCZndDsiIElEPSJ7Yzk4Y2I2NTktY2E4Ni00YTQ1LTg3ZDQtMTBkZjMzMDIxOWU0fSIgVHlwZT0ie0I2RkE2RDhCLUVFQUEtNDdBNi04NDYzLTdGOUE0RjVCQkI2RX0iLz4KICA8TGlua01hbmFnZXIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLmRhdGFjb250cmFjdC5vcmcvMjAwNC8wNy9Qb2xpY3lFeGVjdXRvciIgeG1sbnM6aT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiPgogICAgPGhhc2hzZXQgeG1sbnM6ZDJwMT0iaHR0cDovL3NjaGVtYXMuZGF0YWNvbnRyYWN0Lm9yZy8yMDA0LzA3L1N5c3RlbSI+CiAgICAgIDxkMnAxOlR1cGxlT2ZzdHJpbmdzdHJpbmdzdHJpbmdzdHJpbmc+CiAgICAgICAgPGQycDE6bV9JdGVtMT4xNDgxMTgyNC0wZDQwLTQzYjYtOGY3My1lNzRkNmUzNzA2YzkuU2NyaXB0QWN0aW9uPC9kMnAxOm1fSXRlbTE+CiAgICAgICAgPGQycDE6bV9JdGVtMj5PdXRwdXRPYmplY3Q8L2QycDE6bV9JdGVtMj4KICAgICAgICA8ZDJwMTptX0l0ZW0zLz4KICAgICAgICA8ZDJwMTptX0l0ZW00PlNjcmlwdEFjdGlvbjwvZDJwMTptX0l0ZW00PgogICAgICA8L2QycDE6VHVwbGVPZnN0cmluZ3N0cmluZ3N0cmluZ3N0cmluZz4KICAgICAgPGQycDE6VHVwbGVPZnN0cmluZ3N0cmluZ3N0cmluZ3N0cmluZz4KICAgICAgICA8ZDJwMTptX0l0ZW0xPjE0ODExODI0LTBkNDAtNDNiNi04ZjczLWU3NGQ2ZTM3MDZjOS5TY3JpcHRFeGl0Q29kZTwvZDJwMTptX0l0ZW0xPgogICAgICAgIDxkMnAxOm1fSXRlbTI+T3V0cHV0T2JqZWN0PC9kMnAxOm1fSXRlbTI+CiAgICAgICAgPGQycDE6bV9JdGVtMy8+CiAgICAgICAgPGQycDE6bV9JdGVtND5TY3JpcHRFeGl0Q29kZTwvZDJwMTptX0l0ZW00PgogICAgICA8L2QycDE6VHVwbGVPZnN0cmluZ3N0cmluZ3N0cmluZ3N0cmluZz4KICAgICAgPGQycDE6VHVwbGVPZnN0cmluZ3N0cmluZ3N0cmluZ3N0cmluZz4KICAgICAgICA8ZDJwMTptX0l0ZW0xPjE0ODExODI0LTBkNDAtNDNiNi04ZjczLWU3NGQ2ZTM3MDZjOS5TY3JpcHRMYXN0UmFuPC9kMnAxOm1fSXRlbTE+CiAgICAgICAgPGQycDE6bV9JdGVtMj5PdXRwdXRPYmplY3Q8L2QycDE6bV9JdGVtMj4KICAgICAgICA8ZDJwMTptX0l0ZW0zLz4KICAgICAgICA8ZDJwMTptX0l0ZW00PlNjcmlwdExhc3RSYW48L2QycDE6bV9JdGVtND4KICAgICAgPC9kMnAxOlR1cGxlT2ZzdHJpbmdzdHJpbmdzdHJpbmdzdHJpbmc+CiAgICAgIDxkMnAxOlR1cGxlT2ZzdHJpbmdzdHJpbmdzdHJpbmdzdHJpbmc+CiAgICAgICAgPGQycDE6bV9JdGVtMT4xNDgxMTgyNC0wZDQwLTQzYjYtOGY3My1lNzRkNmUzNzA2YzkuU2NyaXB0TW9kZTwvZDJwMTptX0l0ZW0xPgogICAgICAgIDxkMnAxOm1fSXRlbTI+T3V0cHV0T2JqZWN0PC9kMnAxOm1fSXRlbTI+CiAgICAgICAgPGQycDE6bV9JdGVtMy8+CiAgICAgICAgPGQycDE6bV9JdGVtND5TY3JpcHRNb2RlPC9kMnAxOm1fSXRlbTQ+CiAgICAgIDwvZDJwMTpUdXBsZU9mc3RyaW5nc3RyaW5nc3RyaW5nc3RyaW5nPgogICAgICA8ZDJwMTpUdXBsZU9mc3RyaW5nc3RyaW5nc3RyaW5nc3RyaW5nPgogICAgICAgIDxkMnAxOm1fSXRlbTE+MTQ4MTE4MjQtMGQ0MC00M2I2LThmNzMtZTc0ZDZlMzcwNmM5LlNjcmlwdFJlc3VsdDwvZDJwMTptX0l0ZW0xPgogICAgICAgIDxkMnAxOm1fSXRlbTI+T3V0cHV0T2JqZWN0PC9kMnAxOm1fSXRlbTI+CiAgICAgICAgPGQycDE6bV9JdGVtMy8+CiAgICAgICAgPGQycDE6bV9JdGVtND5TY3JpcHRSZXN1bHQ8L2QycDE6bV9JdGVtND4KICAgICAgPC9kMnAxOlR1cGxlT2ZzdHJpbmdzdHJpbmdzdHJpbmdzdHJpbmc+CiAgICAgIDxkMnAxOlR1cGxlT2ZzdHJpbmdzdHJpbmdzdHJpbmdzdHJpbmc+CiAgICAgICAgPGQycDE6bV9JdGVtMT4xNDgxMTgyNC0wZDQwLTQzYjYtOGY3My1lNzRkNmUzNzA2YzkuU2NyaXB0U2VxdWVuY2U8L2QycDE6bV9JdGVtMT4KICAgICAgICA8ZDJwMTptX0l0ZW0yPk91dHB1dE9iamVjdDwvZDJwMTptX0l0ZW0yPgogICAgICAgIDxkMnAxOm1fSXRlbTMvPgogICAgICAgIDxkMnAxOm1fSXRlbTQ+U2NyaXB0U2VxdWVuY2U8L2QycDE6bV9JdGVtND4KICAgICAgPC9kMnAxOlR1cGxlT2ZzdHJpbmdzdHJpbmdzdHJpbmdzdHJpbmc+CiAgICAgIDxkMnAxOlR1cGxlT2ZzdHJpbmdzdHJpbmdzdHJpbmdzdHJpbmc+CiAgICAgICAgPGQycDE6bV9JdGVtMT4xNDgxMTgyNC0wZDQwLTQzYjYtOGY3My1lNzRkNmUzNzA2YzkuQWdlbnRMYXN0RGlhZ25vc2VkPC9kMnAxOm1fSXRlbTE+CiAgICAgICAgPGQycDE6bV9JdGVtMj5PdXRwdXRPYmplY3Q8L2QycDE6bV9JdGVtMj4KICAgICAgICA8ZDJwMTptX0l0ZW0zLz4KICAgICAgICA8ZDJwMTptX0l0ZW00PkFnZW50TGFzdERpYWdub3NlZDwvZDJwMTptX0l0ZW00PgogICAgICA8L2QycDE6VHVwbGVPZnN0cmluZ3N0cmluZ3N0cmluZ3N0cmluZz4KICAgICAgPGQycDE6VHVwbGVPZnN0cmluZ3N0cmluZ3N0cmluZ3N0cmluZz4KICAgICAgICA8ZDJwMTptX0l0ZW0xPjE0ODExODI0LTBkNDAtNDNiNi04ZjczLWU3NGQ2ZTM3MDZjOS5BZ2VudExhc3RJbnN0YWxsZWQ8L2QycDE6bV9JdGVtMT4KICAgICAgICA8ZDJwMTptX0l0ZW0yPk91dHB1dE9iamVjdDwvZDJwMTptX0l0ZW0yPgogICAgICAgIDxkMnAxOm1fSXRlbTMvPgogICAgICAgIDxkMnAxOm1fSXRlbTQ+QWdlbnRMYXN0SW5zdGFsbGVkPC9kMnAxOm1fSXRlbTQ+CiAgICAgIDwvZDJwMTpUdXBsZU9mc3RyaW5nc3RyaW5nc3RyaW5nc3RyaW5nPgogICAgICA8ZDJwMTpUdXBsZU9mc3RyaW5nc3RyaW5nc3RyaW5nc3RyaW5nPgogICAgICAgIDxkMnAxOm1fSXRlbTE+MTQ4MTE4MjQtMGQ0MC00M2I2LThmNzMtZTc0ZDZlMzcwNmM5LlNjcmlwdFZlcnNpb248L2QycDE6bV9JdGVtMT4KICAgICAgICA8ZDJwMTptX0l0ZW0yPk91dHB1dE9iamVjdDwvZDJwMTptX0l0ZW0yPgogICAgICAgIDxkMnAxOm1fSXRlbTMvPgogICAgICAgIDxkMnAxOm1fSXRlbTQ+U2NyaXB0VmVyc2lvbjwvZDJwMTptX0l0ZW00PgogICAgICA8L2QycDE6VHVwbGVPZnN0cmluZ3N0cmluZ3N0cmluZ3N0cmluZz4KICAgIDwvaGFzaHNldD4KICA8L0xpbmtNYW5hZ2VyPgogIDxEaWFnbm9zdGljcyBPcmlnaW5hbFZlcnNpb249IjIuMTYuMS4xNSIvPgogIDxBY3Rpdml0eSB4bWxucz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9uZXRmeC8yMDA5L3hhbWwvYWN0aXZpdGllcyIgeG1sbnM6bWM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9tYXJrdXAtY29tcGF0aWJpbGl0eS8yMDA2IiB4bWxuczptdmE9ImNsci1uYW1lc3BhY2U6TWljcm9zb2Z0LlZpc3VhbEJhc2ljLkFjdGl2aXRpZXM7YXNzZW1ibHk9U3lzdGVtLkFjdGl2aXRpZXMiIHhtbG5zOnA9ImNsci1uYW1lc3BhY2U6UG9saWN5RXhlY3V0b3I7YXNzZW1ibHk9UG9saWN5RXhlY3V0aW9uRW5naW5lIiB4bWxuczpzPSJjbHItbmFtZXNwYWNlOlN5c3RlbTthc3NlbWJseT1tc2NvcmxpYiIgeG1sbnM6c2Fkcz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9uZXRmeC8yMDEwL3hhbWwvYWN0aXZpdGllcy9kZWJ1Z2dlciIgeG1sbnM6c2FwPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL25ldGZ4LzIwMDkveGFtbC9hY3Rpdml0aWVzL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2NnPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljO2Fzc2VtYmx5PW1zY29ybGliIiB4bWxuczp4PSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbCIgbWM6SWdub3JhYmxlPSJzYWRzIHNhcCIgeDpDbGFzcz0iUG9saWN5IEJ1aWxkZXIiPgogICAgPHg6TWVtYmVycz4KICAgICAgPHg6UHJvcGVydHkgTmFtZT0iUG9saWN5R1VJRCIgVHlwZT0iSW5Bcmd1bWVudCh4OlN0cmluZykiLz4KICAgIDwveDpNZW1iZXJzPgogICAgPHNhcDpWaXJ0dWFsaXplZENvbnRhaW5lclNlcnZpY2UuSGludFNpemU+NTA0LDc0NTwvc2FwOlZpcnR1YWxpemVkQ29udGFpbmVyU2VydmljZS5IaW50U2l6ZT4KICAgIDxtdmE6VmlzdWFsQmFzaWMuU2V0dGluZ3M+QXNzZW1ibHkgcmVmZXJlbmNlcyBhbmQgaW1wb3J0ZWQgbmFtZXNwYWNlcyBzZXJpYWxpemVkIGFzIFhNTCBuYW1lc3BhY2VzPC9tdmE6VmlzdWFsQmFzaWMuU2V0dGluZ3M+CiAgICA8cDpQb2xpY3lTZXF1ZW5jZSBEaXNwbGF5TmFtZT0iUG9saWN5IEJ1aWxkZXIiIG12YTpWaXN1YWxCYXNpYy5TZXR0aW5ncz0iQXNzZW1ibHkgcmVmZXJlbmNlcyBhbmQgaW1wb3J0ZWQgbmFtZXNwYWNlcyBzZXJpYWxpemVkIGFzIFhNTCBuYW1lc3BhY2VzIiBzYXA6VmlydHVhbGl6ZWRDb250YWluZXJTZXJ2aWNlLkhpbnRTaXplPSI1MDQsNzQ1Ij4KICAgICAgPHA6UG9saWN5U2VxdWVuY2UuQWN0aXZpdGllcz4KICAgICAgICA8cDpSdW5Qb3dlclNoZWxsU2NyaXB0IEFzc2VtYmx5TmFtZT0iUG9saWN5RXhlY3V0aW9uRW5naW5lLCBWZXJzaW9uPTIuMTYuMS4xNSwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsIiBEaXNwbGF5TmFtZT0iUnVuIFBvd2VyU2hlbGwgU2NyaXB0IiBNb25pa2VyPSIxNDgxMTgyNC0wZDQwLTQzYjYtOGY3My1lNzRkNmUzNzA2YzkiIE91dFB1dF82ND0iW1J1blBvd2VyU2hlbGxTY3JpcHRfT3V0UHV0XzY0XSIgUmVzdWx0PSJbUnVuUG93ZXJTaGVsbFNjcmlwdF9SZXN1bHRdIiBSZXN1bHRTdHJpbmc9IltSdW5Qb3dlclNoZWxsU2NyaXB0X1Jlc3VsdFN0cmluZ10iIFJlc3VsdHNfeDY0PSJbUnVuUG93ZXJTaGVsbFNjcmlwdF9SZXN1bHRzX3g2NF0iIFJ1bkFzQ3VycmVudExvZ2dlZE9uVXNlcj0iRmFsc2UiIFNjcmlwdEV4ZWN1dGlvbk1ldGhvZD0iRXhlY3V0ZURlYnVnIiBUeXBlTmFtZT0iUnVuUG93ZXJTaGVsbFNjcmlwdCIgZ2VuQXJnRXZlbnQ9IjU5ZmIyNzNiLTJiZTItNDA4NS1iNzNlLWNhMThiY2NjNmFlYyIgbV9iVGV4dExpbmtDaGFuZ2U9IkZhbHNlIiBzYXA6VmlydHVhbGl6ZWRDb250YWluZXJTZXJ2aWNlLkhpbnRTaXplPSI0NjgsNTIyIiBzY3JpcHQ9IlpnQjFBRzRBWXdCMEFHa0Fid0J1QUNBQVVnQmxBR0VBWkFCTEFHVUFlUUFnQUhzQURRQUtBQ0FBSUFBZ0FDQUFJd0FqQUNNQUlBQlFBR0VBY2dCaEFHMEFaUUIwQUdVQWNnQnpBQTBBQ2dBZ0FDQUFJQUFnQUNNQUl3QWpBQ01BSXdBakFDTUFJd0FqQUNNQUl3QWpBQ01BSXdBakFDTUFJd0FqQUNNQUl3QWpBQ01BSXdBakFDTUFJd0FqQUNNQUl3QWpBQ01BRFFBS0FDQUFJQUFnQUNBQWNBQmhBSElBWVFCdEFDQUFLQUFrQUVzQVpRQjVBQ2tBRFFBS0FDQUFJQUFnQUNBQUl3QWpBQ01BSUFCR0FIVUFiZ0JqQUhRQWFRQnZBRzRBSUFCQ0FHOEFaQUI1QUEwQUNnQWdBQ0FBSUFBZ0FDTUFJd0FqQUNNQUl3QWpBQ01BSXdBakFDTUFJd0FqQUNNQUl3QWpBQ01BSXdBakFDTUFJd0FqQUNNQUl3QWpBQ01BSXdBakFDTUFJd0FqQUNNQURRQUtBQ0FBSUFBZ0FDQUFJd0FnQUZRQVpRQnpBSFFBSUFCcEFHWUFJQUIwQUdnQVpRQWdBRXNBWlFCNUFDQUFhUUJtQUNBQVRRQnBBSE1BY3dCcEFHNEFad0FnQUdrQVpnQWdBSE1BYndBZ0FISUFaUUIwQUhVQWNnQnVBQ0FBSkFCdUFIVUFiQUJzQUEwQUNnQWdBQ0FBSUFBZ0FHa0FaZ0FnQUNnQUtBQlVBR1VBY3dCMEFDMEFVQUJoQUhRQWFBQWdBQ1FBU3dCbEFIa0FLUUFnQUMwQVpRQnhBQ0FBSkFCbUFHRUFiQUJ6QUdVQUtRQU5BQW9BSUFBZ0FDQUFJQUI3QUNBQUpBQnVBSFVBYkFCc0FDQUFmUUFnQUEwQUNnQWdBQ0FBSUFBZ0FHVUFiQUJ6QUdVQUlBQjdBQTBBQ2dBZ0FDQUFJQUFnQUNBQUlBQWdBQ0FBUndCbEFIUUFMUUJKQUhRQVpRQnRBRkFBY2dCdkFIQUFaUUJ5QUhRQWVRQWdBQ1FBU3dCbEFIa0FJQUI4QUNBQVV3QmxBR3dBWlFCakFIUUFMUUJQQUdJQWFnQmxBR01BZEFBZ0FDb0FJQUF0QUVVQWVBQmpBR3dBZFFCa0FHVUFVQUJ5QUc4QWNBQmxBSElBZEFCNUFDQUFVQUJUQUNvQURRQUtBQ0FBSUFBZ0FDQUFmUUFOQUFvQWZRQU5BQW9BRFFBS0FDUUFRUUJuQUdVQWJnQjBBRklBWlFCbkFGQUFZUUIwQUdnQUlBQTlBQ0FBSWdCSUFFc0FUQUJOQURvQVhBQlRBRThBUmdCVUFGY0FRUUJTQUVVQVhBQlRBRzhBYkFCaEFISUFkd0JwQUc0QVpBQnpBQ0FBVFFCVEFGQUFJQUJEQUc4QWJRQnRBSFVBYmdCcEFIUUFlUUJjQUVrQWJnQnpBSFFBWVFCc0FHd0FRUUJuQUdVQWJnQjBBQ0lBRFFBS0FDUUFTUUJ1QUhNQWRBQmhBR3dBYkFCQkFHY0FaUUJ1QUhRQVVnQmxBSE1BZFFCc0FIUUFjd0FnQUQwQUlBQlNBR1VBWVFCa0FFc0FaUUI1QUNBQUpBQkJBR2NBWlFCdUFIUUFVZ0JsQUdjQVVBQmhBSFFBYUFBTkFBb0FEUUFLQUNNQUlBQlVBR2dBWlFCekFHVUFJQUIyQUdFQWJBQjFBR1VBSUFCcEFITUFJQUJoQUd3QWJRQnZBSE1BZEFBZ0FHRUFiQUIzQUdFQWVRQnpBQ0FBY0FCeUFHVUFjd0JsQUc0QWRBQU5BQW9BSkFCQkFHY0FaUUJ1QUhRQVRBQmhBSE1BZEFCRUFHa0FZUUJuQUc0QWJ3QnpBR1VBWkFBZ0FEMEFJQUJwQUdZQUlBQW9BQ1FBYmdCMUFHd0FiQUFnQUMwQWJnQmxBQ0FBSkFCSkFHNEFjd0IwQUdFQWJBQnNBRUVBWndCbEFHNEFkQUJTQUdVQWN3QjFBR3dBZEFCekFDNEFRUUJuQUdVQWJnQjBBRXdBWVFCekFIUUFSQUJwQUdFQVp3QnVBRzhBY3dCbEFHUUFLUUFnQUhzQUlBQWtBRWtBYmdCekFIUUFZUUJzQUd3QVFRQm5BR1VBYmdCMEFGSUFaUUJ6QUhVQWJBQjBBSE1BTGdCQkFHY0FaUUJ1QUhRQVRBQmhBSE1BZEFCRUFHa0FZUUJuQUc0QWJ3QnpBR1VBWkFBZ0FIMEFaUUJzQUhNQVpRQWdBSHNBSUFCSEFHVUFkQUF0QUVRQVlRQjBBR1VBSUFBeEFEa0FNQUF3QUNBQWZRQU5BQW9BRFFBS0FDTUFJQUJVQUdnQWFRQnpBQ0FBZGdCaEFHd0FkUUJsQUNBQWFRQnpBQ0FBY0FCeUFHVUFjd0JsQUc0QWRBQWdBR2tBWmdBZ0FIUUFhQUJsQUNBQWN3QmpBSElBYVFCd0FIUUFJQUJvQUdFQWN3QWdBR2tBYmdCekFIUUFZUUJzQUd3QVpRQmtBQ0FBYndCeUFDQUFkUUJ3QUdjQWNnQmhBR1FBWlFCa0FDQUFZUUIwQUNBQWN3QnZBRzBBWlFBZ0FIQUFid0JwQUc0QWRBQU5BQW9BSkFCQkFHY0FaUUJ1QUhRQVRBQmhBSE1BZEFCSkFHNEFjd0IwQUdFQWJBQnNBR1VBWkFBZ0FEMEFJQUJwQUdZQUlBQW9BQ1FBYmdCMUFHd0FiQUFnQUMwQWJnQmxBQ0FBSkFCSkFHNEFjd0IwQUdFQWJBQnNBRUVBWndCbEFHNEFkQUJTQUdVQWN3QjFBR3dBZEFCekFDNEFRUUJuQUdVQWJnQjBBRXdBWVFCekFIUUFTUUJ1QUhNQWRBQmhBR3dBYkFCbEFHUUFLUUFnQUhzQUlBQWtBRWtBYmdCekFIUUFZUUJzQUd3QVFRQm5BR1VBYmdCMEFGSUFaUUJ6QUhVQWJBQjBBSE1BTGdCQkFHY0FaUUJ1QUhRQVRBQmhBSE1BZEFCSkFHNEFjd0IwQUdFQWJBQnNBR1VBWkFBZ0FIMEFJQUJsQUd3QWN3QmxBQ0FBZXdBZ0FFY0FaUUIwQUMwQVJBQmhBSFFBWlFBZ0FERUFPUUF3QURBQUlBQjlBQTBBQ2dBTkFBb0FJd0FnQUZRQWFBQmxBSE1BWlFBZ0FIWUFZUUJzQUhVQVpRQnpBQ0FBWVFCeUFHVUFJQUJoQUd3QWR3QmhBSGtBY3dBZ0FIQUFjZ0JsQUhNQVpRQnVBSFFBRFFBS0FDUUFVd0JqQUhJQWFRQndBSFFBUVFCakFIUUFhUUJ2QUc0QUlBQTlBQ0FBYVFCbUFDQUFLQUFrQUc0QWRRQnNBR3dBSUFBdEFHNEFaUUFnQUNRQVNRQnVBSE1BZEFCaEFHd0FiQUJCQUdjQVpRQnVBSFFBVWdCbEFITUFkUUJzQUhRQWN3QXVBRk1BWXdCeUFHa0FjQUIwQUVFQVl3QjBBR2tBYndCdUFDa0FJQUI3QUNBQUpBQkpBRzRBY3dCMEFHRUFiQUJzQUVFQVp3QmxBRzRBZEFCU0FHVUFjd0IxQUd3QWRBQnpBQzRBVXdCakFISUFhUUJ3QUhRQVFRQmpBSFFBYVFCdkFHNEFJQUI5QUNBQVpRQnNBSE1BWlFBZ0FIc0FJQUFuQUMwQUp3QWdBSDBBRFFBS0FDUUFVd0JqQUhJQWFRQndBSFFBUlFCNEFHa0FkQUJEQUc4QVpBQmxBQ0FBUFFBZ0FHa0FaZ0FnQUNnQUpBQnVBSFVBYkFCc0FDQUFMUUJ1QUdVQUlBQWtBRWtBYmdCekFIUUFZUUJzQUd3QVFRQm5BR1VBYmdCMEFGSUFaUUJ6QUhVQWJBQjBBSE1BTGdCVEFHTUFjZ0JwQUhBQWRBQkZBSGdBYVFCMEFFTUFid0JrQUdVQUtRQWdBSHNBSUFBa0FHNEFkUUJzQUd3QUlBQXRBRzRBWlFBZ0FDUUFTUUJ1QUhNQWRBQmhBR3dBYkFCQkFHY0FaUUJ1QUhRQVVnQmxBSE1BZFFCc0FIUUFjd0F1QUZNQVl3QnlBR2tBY0FCMEFFVUFlQUJwQUhRQVF3QnZBR1FBWlFBZ0FIMEFJQUJsQUd3QWN3QmxBQ0FBZXdBZ0FEUUFNQUEwQUNBQWZRQU5BQW9BSkFCVEFHTUFjZ0JwQUhBQWRBQk1BR0VBY3dCMEFGSUFZUUJ1QUNBQVBRQWdBR2tBWmdBZ0FDZ0FKQUJ1QUhVQWJBQnNBQ0FBTFFCdUFHVUFJQUFrQUVrQWJnQnpBSFFBWVFCc0FHd0FRUUJuQUdVQWJnQjBBRklBWlFCekFIVUFiQUIwQUhNQUxnQlRBR01BY2dCcEFIQUFkQUJNQUdFQWN3QjBBRklBWVFCdUFDa0FJQUI3QUNBQUpBQkpBRzRBY3dCMEFHRUFiQUJzQUVFQVp3QmxBRzRBZEFCU0FHVUFjd0IxQUd3QWRBQnpBQzRBVXdCakFISUFhUUJ3QUhRQVRBQmhBSE1BZEFCU0FHRUFiZ0FnQUgwQUlBQmxBR3dBY3dCbEFDQUFld0FnQUVjQVpRQjBBQzBBUkFCaEFIUUFaUUFnQURFQU9RQXdBREFBSUFCOUFBMEFDZ0FrQUZNQVl3QnlBR2tBY0FCMEFFMEFid0JrQUdVQUlBQTlBQ0FBYVFCbUFDQUFLQUFrQUc0QWRRQnNBR3dBSUFBdEFHNEFaUUFnQUNRQVNRQnVBSE1BZEFCaEFHd0FiQUJCQUdjQVpRQnVBSFFBVWdCbEFITUFkUUJzQUhRQWN3QXVBRk1BWXdCeUFHa0FjQUIwQUUwQWJ3QmtBR1VBS1FBZ0FIc0FJQUFrQUVrQWJnQnpBSFFBWVFCc0FHd0FRUUJuQUdVQWJnQjBBRklBWlFCekFIVUFiQUIwQUhNQUxnQlRBR01BY2dCcEFIQUFkQUJOQUc4QVpBQmxBQ0FBZlFBZ0FHVUFiQUJ6QUdVQUlBQjdBQ0FBSndBdEFDY0FJQUI5QUEwQUNnQWtBRk1BWXdCeUFHa0FjQUIwQUZJQVpRQnpBSFVBYkFCMEFDQUFQUUFnQUdrQVpnQWdBQ2dBSkFCdUFIVUFiQUJzQUNBQUxRQnVBR1VBSUFBa0FFa0FiZ0J6QUhRQVlRQnNBR3dBUVFCbkFHVUFiZ0IwQUZJQVpRQnpBSFVBYkFCMEFITUFMZ0JUQUdNQWNnQnBBSEFBZEFCU0FHVUFjd0IxQUd3QWRBQXBBQ0FBZXdBZ0FDUUFTUUJ1QUhNQWRBQmhBR3dBYkFCQkFHY0FaUUJ1QUhRQVVnQmxBSE1BZFFCc0FIUUFjd0F1QUZNQVl3QnlBR2tBY0FCMEFGTUFaUUJ4QUhVQVpRQnVBR01BWlFBZ0FIMEFJQUJsQUd3QWN3QmxBQ0FBZXdBZ0FDY0FMUUFuQUNBQWZRQU5BQW9BSkFCVEFHTUFjZ0JwQUhBQWRBQlRBR1VBY1FCMUFHVUFiZ0JqQUdVQUlBQTlBQ0FBYVFCbUFDQUFLQUFrQUc0QWRRQnNBR3dBSUFBdEFHNEFaUUFnQUNRQVNRQnVBSE1BZEFCaEFHd0FiQUJCQUdjQVpRQnVBSFFBVWdCbEFITUFkUUJzQUhRQWN3QXVBRk1BWXdCeUFHa0FjQUIwQUZNQVpRQnhBSFVBWlFCdUFHTUFaUUFwQUNBQWV3QWdBQ1FBU1FCdUFITUFkQUJoQUd3QWJBQkJBR2NBWlFCdUFIUUFVZ0JsQUhNQWRRQnNBSFFBY3dBdUFGTUFZd0J5QUdrQWNBQjBBRk1BWlFCeEFIVUFaUUJ1QUdNQVpRQWdBSDBBSUFCbEFHd0Fjd0JsQUNBQWV3QWdBQ2NBTFFBbkFDQUFmUUFOQUFvQUpBQlRBR01BY2dCcEFIQUFkQUJXQUdVQWNnQnpBR2tBYndCdUFDQUFQUUFnQUdrQVpnQWdBQ2dBSkFCdUFIVUFiQUJzQUNBQUxRQnVBR1VBSUFBa0FFa0FiZ0J6QUhRQVlRQnNBR3dBUVFCbkFHVUFiZ0IwQUZJQVpRQnpBSFVBYkFCMEFITUFMZ0JUQUdNQWNnQnBBSEFBZEFCV0FHVUFjZ0J6QUdrQWJ3QnVBQ2tBSUFCN0FDQUFXd0JwQUc0QWRBQmRBQ1FBU1FCdUFITUFkQUJoQUd3QWJBQkJBR2NBWlFCdUFIUUFVZ0JsQUhNQWRRQnNBSFFBY3dBdUFGTUFZd0J5QUdrQWNBQjBBRllBWlFCeUFITUFhUUJ2QUc0QUxnQlNBR1VBY0FCc0FHRUFZd0JsQUNnQUp3QXVBQ2NBTEFBZ0FDY0FKd0FwQUNBQWZRQWdBR1VBYkFCekFHVUFJQUI3QUNBQU5BQXdBRFFBSUFCOUFBPT0iPgogICAgICAgICAgPHA6UnVuUG93ZXJTaGVsbFNjcmlwdC5JbkFyZ3M+CiAgICAgICAgICAgIDxzY2c6RGljdGlvbmFyeSB4OlR5cGVBcmd1bWVudHM9Ing6U3RyaW5nLCBwOkluQXJnIi8+CiAgICAgICAgICA8L3A6UnVuUG93ZXJTaGVsbFNjcmlwdC5JbkFyZ3M+CiAgICAgICAgICA8cDpSdW5Qb3dlclNoZWxsU2NyaXB0Lk91dEFyZ3M+CiAgICAgICAgICAgIDxwOk91dEFyZyBBcmdUeXBlPSJzdHJpbmciIERpc3BsYXlOYW1lPSJTY3JpcHQgQWN0aW9uIiBOYW1lPSJTY3JpcHRBY3Rpb24iIHg6S2V5PSJTY3JpcHRBY3Rpb24iPgogICAgICAgICAgICAgIDxwOk91dEFyZy5Bcmc+CiAgICAgICAgICAgICAgICA8T3V0QXJndW1lbnQgeDpUeXBlQXJndW1lbnRzPSJ4OlN0cmluZyI+W1J1blBvd2VyU2hlbGxTY3JpcHRfU2NyaXB0QWN0aW9uXTwvT3V0QXJndW1lbnQ+CiAgICAgICAgICAgICAgPC9wOk91dEFyZy5Bcmc+CiAgICAgICAgICAgIDwvcDpPdXRBcmc+CiAgICAgICAgICAgIDxwOk91dEFyZyBBcmdUeXBlPSJudW1iZXIiIERpc3BsYXlOYW1lPSJTY3JpcHQgRXhpdCBDb2RlIiBOYW1lPSJTY3JpcHRFeGl0Q29kZSIgeDpLZXk9IlNjcmlwdEV4aXRDb2RlIj4KICAgICAgICAgICAgICA8cDpPdXRBcmcuQXJnPgogICAgICAgICAgICAgICAgPE91dEFyZ3VtZW50IHg6VHlwZUFyZ3VtZW50cz0ieDpEb3VibGUiPltSdW5Qb3dlclNoZWxsU2NyaXB0X1NjcmlwdEV4aXRDb2RlXTwvT3V0QXJndW1lbnQ+CiAgICAgICAgICAgICAgPC9wOk91dEFyZy5Bcmc+CiAgICAgICAgICAgIDwvcDpPdXRBcmc+CiAgICAgICAgICAgIDxwOk91dEFyZyBBcmdUeXBlPSJkYXRlIiBEaXNwbGF5TmFtZT0iU2NyaXB0IExhc3QgUmFuIiBOYW1lPSJTY3JpcHRMYXN0UmFuIiB4OktleT0iU2NyaXB0TGFzdFJhbiI+CiAgICAgICAgICAgICAgPHA6T3V0QXJnLkFyZz4KICAgICAgICAgICAgICAgIDxPdXRBcmd1bWVudCB4OlR5cGVBcmd1bWVudHM9InM6RGF0ZVRpbWUiPltSdW5Qb3dlclNoZWxsU2NyaXB0X1NjcmlwdExhc3RSYW5dPC9PdXRBcmd1bWVudD4KICAgICAgICAgICAgICA8L3A6T3V0QXJnLkFyZz4KICAgICAgICAgICAgPC9wOk91dEFyZz4KICAgICAgICAgICAgPHA6T3V0QXJnIEFyZ1R5cGU9InN0cmluZyIgRGlzcGxheU5hbWU9IlNjcmlwdCBNb2RlIiBOYW1lPSJTY3JpcHRNb2RlIiB4OktleT0iU2NyaXB0TW9kZSI+CiAgICAgICAgICAgICAgPHA6T3V0QXJnLkFyZz4KICAgICAgICAgICAgICAgIDxPdXRBcmd1bWVudCB4OlR5cGVBcmd1bWVudHM9Ing6U3RyaW5nIj5bUnVuUG93ZXJTaGVsbFNjcmlwdF9TY3JpcHRNb2RlXTwvT3V0QXJndW1lbnQ+CiAgICAgICAgICAgICAgPC9wOk91dEFyZy5Bcmc+CiAgICAgICAgICAgIDwvcDpPdXRBcmc+CiAgICAgICAgICAgIDxwOk91dEFyZyBBcmdUeXBlPSJzdHJpbmciIERpc3BsYXlOYW1lPSJTY3JpcHQgUmVzdWx0IiBOYW1lPSJTY3JpcHRSZXN1bHQiIHg6S2V5PSJTY3JpcHRSZXN1bHQiPgogICAgICAgICAgICAgIDxwOk91dEFyZy5Bcmc+CiAgICAgICAgICAgICAgICA8T3V0QXJndW1lbnQgeDpUeXBlQXJndW1lbnRzPSJ4OlN0cmluZyI+W1J1blBvd2VyU2hlbGxTY3JpcHRfU2NyaXB0UmVzdWx0XTwvT3V0QXJndW1lbnQ+CiAgICAgICAgICAgICAgPC9wOk91dEFyZy5Bcmc+CiAgICAgICAgICAgIDwvcDpPdXRBcmc+CiAgICAgICAgICAgIDxwOk91dEFyZyBBcmdUeXBlPSJzdHJpbmciIERpc3BsYXlOYW1lPSJTY3JpcHQgU2VxdWVuY2UiIE5hbWU9IlNjcmlwdFNlcXVlbmNlIiB4OktleT0iU2NyaXB0U2VxdWVuY2UiPgogICAgICAgICAgICAgIDxwOk91dEFyZy5Bcmc+CiAgICAgICAgICAgICAgICA8T3V0QXJndW1lbnQgeDpUeXBlQXJndW1lbnRzPSJ4OlN0cmluZyI+W1J1blBvd2VyU2hlbGxTY3JpcHRfU2NyaXB0U2VxdWVuY2VdPC9PdXRBcmd1bWVudD4KICAgICAgICAgICAgICA8L3A6T3V0QXJnLkFyZz4KICAgICAgICAgICAgPC9wOk91dEFyZz4KICAgICAgICAgICAgPHA6T3V0QXJnIEFyZ1R5cGU9Im51bWJlciIgRGlzcGxheU5hbWU9IlNjcmlwdCBWZXJzaW9uIiBOYW1lPSJTY3JpcHRWZXJzaW9uIiB4OktleT0iU2NyaXB0VmVyc2lvbiI+CiAgICAgICAgICAgICAgPHA6T3V0QXJnLkFyZz4KICAgICAgICAgICAgICAgIDxPdXRBcmd1bWVudCB4OlR5cGVBcmd1bWVudHM9Ing6RG91YmxlIj5bUnVuUG93ZXJTaGVsbFNjcmlwdF9TY3JpcHRWZXJzaW9uXTwvT3V0QXJndW1lbnQ+CiAgICAgICAgICAgICAgPC9wOk91dEFyZy5Bcmc+CiAgICAgICAgICAgIDwvcDpPdXRBcmc+CiAgICAgICAgICAgIDxwOk91dEFyZyBBcmdUeXBlPSJkYXRlIiBEaXNwbGF5TmFtZT0iQWdlbnQgTGFzdCBEaWFnbm9zZWQiIE5hbWU9IkFnZW50TGFzdERpYWdub3NlZCIgeDpLZXk9IkFnZW50TGFzdERpYWdub3NlZCI+CiAgICAgICAgICAgICAgPHA6T3V0QXJnLkFyZz4KICAgICAgICAgICAgICAgIDxPdXRBcmd1bWVudCB4OlR5cGVBcmd1bWVudHM9InM6RGF0ZVRpbWUiPltSdW5Qb3dlclNoZWxsU2NyaXB0X0FnZW50TGFzdERpYWdub3NlZF08L091dEFyZ3VtZW50PgogICAgICAgICAgICAgIDwvcDpPdXRBcmcuQXJnPgogICAgICAgICAgICA8L3A6T3V0QXJnPgogICAgICAgICAgICA8cDpPdXRBcmcgQXJnVHlwZT0iZGF0ZSIgRGlzcGxheU5hbWU9IkFnZW50IExhc3QgSW5zdGFsbGVkIiBOYW1lPSJBZ2VudExhc3RJbnN0YWxsZWQiIHg6S2V5PSJBZ2VudExhc3RJbnN0YWxsZWQiPgogICAgICAgICAgICAgIDxwOk91dEFyZy5Bcmc+CiAgICAgICAgICAgICAgICA8T3V0QXJndW1lbnQgeDpUeXBlQXJndW1lbnRzPSJzOkRhdGVUaW1lIj5bUnVuUG93ZXJTaGVsbFNjcmlwdF9BZ2VudExhc3RJbnN0YWxsZWRdPC9PdXRBcmd1bWVudD4KICAgICAgICAgICAgICA8L3A6T3V0QXJnLkFyZz4KICAgICAgICAgICAgPC9wOk91dEFyZz4KICAgICAgICAgIDwvcDpSdW5Qb3dlclNoZWxsU2NyaXB0Lk91dEFyZ3M+CiAgICAgICAgPC9wOlJ1blBvd2VyU2hlbGxTY3JpcHQ+CiAgICAgIDwvcDpQb2xpY3lTZXF1ZW5jZS5BY3Rpdml0aWVzPgogICAgICA8cDpQb2xpY3lTZXF1ZW5jZS5WYXJpYWJsZXM+CiAgICAgICAgPFZhcmlhYmxlIE5hbWU9IlJ1blBvd2VyU2hlbGxTY3JpcHRfT3V0UHV0XzY0IiB4OlR5cGVBcmd1bWVudHM9Ing6U3RyaW5nIi8+CiAgICAgICAgPFZhcmlhYmxlIE5hbWU9IlJ1blBvd2VyU2hlbGxTY3JpcHRfUmVzdWx0U3RyaW5nIiB4OlR5cGVBcmd1bWVudHM9Ing6U3RyaW5nIi8+CiAgICAgICAgPFZhcmlhYmxlIE5hbWU9IlJ1blBvd2VyU2hlbGxTY3JpcHRfUmVzdWx0c194NjQiIHg6VHlwZUFyZ3VtZW50cz0ic2NnOklFbnVtZXJhYmxlKHg6T2JqZWN0KSIvPgogICAgICAgIDxWYXJpYWJsZSBOYW1lPSJSdW5Qb3dlclNoZWxsU2NyaXB0X1Jlc3VsdCIgeDpUeXBlQXJndW1lbnRzPSJ4OkRvdWJsZSIvPgogICAgICAgIDxWYXJpYWJsZSBOYW1lPSJSdW5Qb3dlclNoZWxsU2NyaXB0X1NjcmlwdEFjdGlvbiIgeDpUeXBlQXJndW1lbnRzPSJ4OlN0cmluZyIvPgogICAgICAgIDxWYXJpYWJsZSBOYW1lPSJSdW5Qb3dlclNoZWxsU2NyaXB0X1NjcmlwdEV4aXRDb2RlIiB4OlR5cGVBcmd1bWVudHM9Ing6RG91YmxlIi8+CiAgICAgICAgPFZhcmlhYmxlIE5hbWU9IlJ1blBvd2VyU2hlbGxTY3JpcHRfU2NyaXB0TGFzdFJhbiIgeDpUeXBlQXJndW1lbnRzPSJzOkRhdGVUaW1lIi8+CiAgICAgICAgPFZhcmlhYmxlIE5hbWU9IlJ1blBvd2VyU2hlbGxTY3JpcHRfU2NyaXB0TW9kZSIgeDpUeXBlQXJndW1lbnRzPSJ4OlN0cmluZyIvPgogICAgICAgIDxWYXJpYWJsZSBOYW1lPSJSdW5Qb3dlclNoZWxsU2NyaXB0X1NjcmlwdFJlc3VsdCIgeDpUeXBlQXJndW1lbnRzPSJ4OlN0cmluZyIvPgogICAgICAgIDxWYXJpYWJsZSBOYW1lPSJSdW5Qb3dlclNoZWxsU2NyaXB0X1NjcmlwdFNlcXVlbmNlIiB4OlR5cGVBcmd1bWVudHM9Ing6U3RyaW5nIi8+CiAgICAgICAgPFZhcmlhYmxlIE5hbWU9IlJ1blBvd2VyU2hlbGxTY3JpcHRfQWdlbnRMYXN0RGlhZ25vc2VkIiB4OlR5cGVBcmd1bWVudHM9InM6RGF0ZVRpbWUiLz4KICAgICAgICA8VmFyaWFibGUgTmFtZT0iUnVuUG93ZXJTaGVsbFNjcmlwdF9BZ2VudExhc3RJbnN0YWxsZWQiIHg6VHlwZUFyZ3VtZW50cz0iczpEYXRlVGltZSIvPgogICAgICAgIDxWYXJpYWJsZSBOYW1lPSJSdW5Qb3dlclNoZWxsU2NyaXB0X1NjcmlwdFZlcnNpb24iIHg6VHlwZUFyZ3VtZW50cz0ieDpEb3VibGUiLz4KICAgICAgPC9wOlBvbGljeVNlcXVlbmNlLlZhcmlhYmxlcz4KICAgIDwvcDpQb2xpY3lTZXF1ZW5jZT4KICA8L0FjdGl2aXR5Pgo8L1BvbGljeT4= + + + + \ No newline at end of file diff --git a/Custom Service Package/CustomService.ps1 b/Custom Service Package/CustomService.ps1 new file mode 100644 index 0000000..38a9def --- /dev/null +++ b/Custom Service Package/CustomService.ps1 @@ -0,0 +1,38 @@ +function ReadKey { + ### Parameters + ############################### + param ($Key) + ### Function Body + ############################### + # Test if the Key if Missing if so return $null + if ((Test-Path $Key) -eq $false) + { $null } + else { + Get-ItemProperty $Key | Select-Object * -ExcludeProperty PS* + } +} + +$AgentRegPath = "HKLM:\SOFTWARE\N-Able Community\InstallAgent" +$OldAgentRegPath = "HKLM:\SOFTWARE\SolarWinds MSP Community\InstallAgent" +if (Test-Path $AgentRegPath){ + $Path = $AgentRegPath +} else { + $Path = $OldAgentRegPath +} + +$InstallAgentResults = ReadKey $Path + +# These value is almost always present +$AgentLastDiagnosed = if ($null -ne $InstallAgentResults.AgentLastDiagnosed) { $InstallAgentResults.AgentLastDiagnosed }else { Get-Date 1900 } + +# This value is present if the script has installed or upgraded at some point +$AgentLastInstalled = if ($null -ne $InstallAgentResults.AgentLastInstalled) { $InstallAgentResults.AgentLastInstalled } else { Get-Date 1900 } + +# These values are always present +$ScriptAction = if ($null -ne $InstallAgentResults.ScriptAction) { $InstallAgentResults.ScriptAction } else { '-' } +$ScriptExitCode = if ($null -ne $InstallAgentResults.ScriptExitCode) { $null -ne $InstallAgentResults.ScriptExitCode } else { 404 } +$ScriptLastRan = if ($null -ne $InstallAgentResults.ScriptLastRan) { $InstallAgentResults.ScriptLastRan } else { Get-Date 1900 } +$ScriptMode = if ($null -ne $InstallAgentResults.ScriptMode) { $InstallAgentResults.ScriptMode } else { '-' } +$ScriptResult = if ($null -ne $InstallAgentResults.ScriptResult) { $InstallAgentResults.ScriptSequence } else { '-' } +$ScriptSequence = if ($null -ne $InstallAgentResults.ScriptSequence) { $InstallAgentResults.ScriptSequence } else { '-' } +$ScriptVersion = if ($null -ne $InstallAgentResults.ScriptVersion) { [int]$InstallAgentResults.ScriptVersion.Replace('.', '') } else { 404 } \ No newline at end of file diff --git a/Deployment Package/AGENT/InstallAgent.ps1 b/Deployment Package/AGENT/InstallAgent.ps1 deleted file mode 100644 index 14f40b9..0000000 --- a/Deployment Package/AGENT/InstallAgent.ps1 +++ /dev/null @@ -1,736 +0,0 @@ -# Installation, Diagnostic and Repair Script for the N-Central Agent -# Original Script Created by Tim Wiser -# Maintained by the Solarwinds MSP Community - -################################ -########## Change Log ########## -################################ - -### 5.0.1 on 2019-08-26 - Ryan Crowther Jr -################################################################## -# FIXES/FEATURES -# - Added Detection Support for .NET Framework 4.8 -# - Fixed an issue where a newer Agent Version was not Detected because of the bizarre Windows -# Versioning method for the Agent Installer, for example Version 12.1.2008.0 (12.1 HF1) is -# "greater than" Version 12.1.10241.0 (12.1 SP1 HF1) -# - Added Script Instance Awareness -# - The Script will first check to see if another Instance is already in progress, and if so, -# terminate the Script with an Event Log entry indicating this, in order to preserve Registry -# results of the pre-existing Instance -# - If the pre-existing Instance has been active for more than 30 minutes, the Script will -# proceed anyway, thus overwriting results -# - Removed references to the PS 3.0 function Get-CIMInstance (from a previous optimization) to -# maintain PS 2.0 Compatibility -# - Fixed a premature stop error in the Launcher when a Device has .NET 2.0 SP1 installed, but -# needs to install PowerShell 2.0 (Thank you, Harvey!) -# - Fixed an issue during Diagnosis phase where incorrect Service Startup Behavior was ALWAYS -# detected, even after Repairs complete successfully - -### 5.0.0 on 2018-11-08 - Ryan Crowther Jr -################################################################## -# OPTIMIZATION -# - Converted InstallAgent.vbs to PowerShell 2.0 Compatible Script (InstallAgent.ps1) (SEE NOTES -# SECTION BELOW) -# - Converted InstallAgent.ini to XML file (PartnerConfig.xml) for direct parsing and variable -# typing in PowerShell 2.0 -# FIXES/FEATURES -# - Reworked Windows Event Log Reporting -# 1 - Detailed Script Results are packaged in a single Event -# 2 - Missing/Invalid Items required by the Script are identified along with provided resolutions -# 3 - Problems discovered with the Agent are listed in addition to Repair actions taken and their -# results -# 4 - Details regarding Installers used and Install Methods attempted -# - Reworked Windows Registry Reporting -# 1 - Prerequisite Launcher now stores Execution values in the same root key as the Setup Script -# 2 - Action and Sequence Updates are made to the Registry in real time as the Script progresses -# - Added Local Agent Activation Info retention (Location specified in Partner Configuration) -# - Added Activation Key Builder (based on Appliance Info) -# - Script prioritizes Activation Info as follows: -# 1 - Discovered Activation Key (currently installed Agent) -# 2 - Discovered Customer/Site ID (currently installed Agent) -# 3 - Historical Activation Key (Local History File) -# 4 - Historical Customer/Site ID (Local History File) -# 5 - Default Customer ID for New Devices (GPO/Command-Line Parameter) -# 6 - Historical Default Customer ID (Local History File, if no GPO/Command-Line Parameter is -# Present/Valid) -# - Added Repair for Invalid Appliance ID in ApplianceConfig.xml, typically -1, which causes the -# N-Central Server to be unable to map the Agent to a Device (results in an Agent that either -# never Imports or spontaneously dies) -# - Added Legacy Agent parameters to Partner Configuration to support installation/repair of an -# older Agent on Windows XP/Server 2003 (provided Agent copy is 11.0.0.1114 aka 11.0 HF3) -# - Added Takeover Action for Rogue/Competitor-Controlled Agents (if the configured Server in the -# current installation does not match the N-Central Server Address in the Partner Configuration, -# the Agent will be reinstalled using the Script Customer ID - this also fixes an Agent -# configured with "localhost" N-Central Server Address) -# HOUSEKEEPING -# - Re-published Change Log with most recent developments up top and some basic Categories for -# updates -# - Moved Script Execution Registry Key to HKLM:\SOFTWARE\Solarwinds MSP Community -# - Added a Legacy Version Cleanup section which will automatically remove values/files created by -# older versions of the Script (Huge thanks to Tim and Jon for their contributions!) -# -### NOTES ON POWERSHELL 2.0 CONVERSION IN 5.0.0 -################################################################## -# The intent of the conversion is to: -# 1 - Maintain currency of the Scripting Platform and key features -# 2 - Remove the need to pass Configuration variables between Scripts -# 3 - Remove lesser-used/deprecated features of the original VBScript -# 4 - Categorize Script actions by Sequence for better reporting clarity -# 5 - Simplify and organize Script body by making use of PowerShell Modules - -### 4.26 on 2018-10-17 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Fixed strScriptPath bad declaration - -### 4.25 on 2018-01-28 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Detect whether .ini file is saved with ASCII encoding (Log error and exit if not) - -### 4.24 on 2017-10-16 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Rebased on .NET 4.5.2 -# - Reorganized prerequisite checks - -### 4.23 on 2017-10-02 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Bug fix on checking executable path (Special Thanks to Rod Clark!) - -### 4.22 on 2017-06-21 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Close case where service is registered but executable is missing - -### 4.21 on 2017-01-26 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Error checking for missing or empty configuration file - -### 4.20 on 2017-01-19 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Moved partner-configured parameters out to AgentInstall.ini -# - Removed Windows 2000 checks -# - Cleaned up agent checks to eliminate redundant calls to StripAgent -# - Remove STARTUP|SHUTDOWN mode - -### 4.10 on 2015-11-15 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Aligned XP < SP3 exit code with documentation (was 3, should be 1) -# - Added localhost zombie checking -# HOUSEKEEPING -# - Changed registry location to HKLM:\Software\N-Central -# OPTIMIZATION -# - Refactored code (SEE NOTES SECTION BELOW) -# - Moved mainline code to subroutines, replaced literals with CONSTs -# -### NOTES ON REFACTORING IN 4.10 -################################################################## -# The intent of the refactoring is: -# 1 - Shorten and simplify the mainline of code by moving larger sections of mainline code to -# subroutines -# 2 - Replace areas where the code quit from subroutines and functions with updates to runState -# variable and flow control in the mainline. The script will quit the mainline with its final -# runState. -# 3 - Remove the duplication of code -# 4 - Remove inaccessible code - -### 4.01 on 2015-11-09 - Jon Czerwinski -################################################################## -# FIXES/FEATURES -# - Corrected agent version zombie check - -### 4.00 on 2015-02-11 - Tim Wiser -################################################################## -# FIXES/FEATURES -# - Formatting changes and more friendly startup message -# - Dirty exit now shows error message and contact information on console -# - Added 'Checking files' bit to remove confusing delay at that stage (No spinner though, -# unfortunately) -# HOUSEKEEPING -# - Final Release by Tim Wiser :o( -# - Committed to github by Jon Czerwinski - -########################################## -########## Constant Definitions ########## -########################################## - -### Command-Line/Group Policy Parameters -################################################################## -param ( - [Parameter(Mandatory=$true)] - $LauncherPath, - [Parameter(Mandatory=$false)] - $CustomerID -) - -### N-Central Constants -################################################################## -### Current Values -# Execution Constants -$NC = @{} -# Install Constants -$NC.InstallParameters = @{ - "A" = "AGENTACTIVATIONKEY" - "B" = "CUSTOMERID" - "C" = "CUSTOMERSPECIFIC" - "D" = "SERVERADDRESS" - "E" = "SERVERPORT" - "F" = "SERVERPROTOCOL" - "G" = "AGENTPROXY" -} -# Path Constants -$NC.Paths = @{ - "BinFolder" = "N-Able Technologies\Windows Agent\bin" - "ConfigFolder" = "N-Able Technologies\Windows Agent\config" - "UninstallKey32" = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" - "UninstallKey64" = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" -} -# Product Constants -$NC.Products = @{ - "Agent" = @{ - "ApplianceConfig" = "ApplianceConfig.xml" - "ApplianceConfigBackup" = "ApplianceConfig.xml.backup" - "IDName" = "N-Central Customer ID" - "InstallLog" = "Checker.log" - "InstallLogFields" = @( - "Activation Key", - "Appliance ID", - "Customer ID", - "Install Time", - "Package Version", - "Server Endpoint" - ) - "InstallerName" = "Windows Agent Installer" - "MaintenanceProcess" = "AgentMaint.exe" - "MaintenanceService" = "Windows Agent Maintenance Service" - "Name" = "N-Central Agent" - "Process" = "agent.exe" - "ServerConfig" = "ServerConfig.xml" - "ServerConfigBackup" = "ServerConfig.xml.backup" - "ServerDefaultValue" = "localhost" - "Service" = "Windows Agent Service" - "WindowsName" = "Windows Agent" - } - "NCServer" = @{ - "Name" = "Partner N-Central Server" - } -} -# Validation Constants -$NC.Validation = @{ - "ActivationKey" = @{ "Encoded" = '^[a-zA-Z0-9+/]{25,}={0,2}$' } - "ApplianceID" = '^[0-9]{5,}$' - "CustomerID" = '^[0-9]{3,4}$' - "ServerAddress" = @{ - "Accepted" = '^[a-zA-Z]{3,}://[a-zA-Z_0-9\.\-]+$' - "Valid" = '^[a-zA-Z_0-9\.\-]+$' - } -} - -### Script Constants -################################################################## -### Current Values -# Execution Constants -$SC = @{ - "DateFormat" = @{ - "FullMessageOnly" = "%Y-%m-%d at %r" - "Full" = "%Y-%m-%d %r" - "Short" = "%Y-%m-%d" - } - "ExecutionMode" = @{ - "A" = "On-Demand" - "B" = "Group Policy" - } - "ErrorScriptResult" = "Script Terminated Unexpectedly" - "InitialScriptAction" = "Importing Function Library" - "InitialScriptResult" = "Script In Progress" - "InstallKit" = @{ - "A" = "None Eligible" - "B" = "Group Policy" - "C" = "On-Demand" - } - "RunningInstanceTimeout" = 30 - "ScriptEventLog" = "Application" - "ScriptVersion" = "5.0.1" - "SuccessScriptAction" = "Graceful Exit" - "SuccessScriptResult" = "Script Completed Successfully" -} -# Appliance Status Constants -$SC.ApplianceStatus = @{ - "A" = "Optimal" - "B" = "Marginal" - "C" = "Orphaned" - "D" = "Disabled" - "E" = "Rogue / Competitor-Controlled" - "F" = "Corrupt" - "G" = "Missing" -} -# Exit Code Constants -$SC.ExitTypes = @{ - "A" = "Successful" - "B" = "Check Configuration" - "C" = "Server Unavailable" - "D" = "Unsuccessful" - "E" = "Report This Error" -} -$SC.Exit = @{ - "Error" = @{ - "ExitResult" = "Undocumented Error (See Event Log)" - "ExitType" = $SC.ExitTypes.E - } - "A" = @{ - "ExitResult" = $SC.SuccessScriptResult - "ExitType" = $SC.ExitTypes.A - } - "B" = @{ - "ExitResult" = "Partner Configuration File is Missing" - "ExitType" = $SC.ExitTypes.B - } - "C" = @{ - "ExitResult" = "Partner Configuration is Invalid" - "ExitType" = $SC.ExitTypes.B - } - "D" = @{ - "ExitResult" = "No Installation Sources Available" - "ExitType" = $SC.ExitTypes.B - } - "E" = @{ - "ExitResult" = "Installer File is Missing" - "ExitType" = $SC.ExitTypes.B - } - "F" = @{ - "ExitResult" = "Installer Version Mismatch" - "ExitType" = $SC.ExitTypes.B - } - "G" = @{ - "ExitResult" = ("Unable to Reach " + $NC.Products.NCServer.Name) - "ExitType" = $SC.ExitTypes.C - } - "H" = @{ - "ExitResult" = "Customer ID Parameter Required" - "ExitType" = $SC.ExitTypes.B - } - "I" = @{ - "ExitResult" = "Customer ID Parameter Invalid" - "ExitType" = $SC.ExitTypes.B - } - "J" = @{ - "ExitResult" = "Windows Installer Service Unavailable" - "ExitType" = $SC.ExitTypes.D - } - "K" = @{ - "ExitResult" = ".NET Framework Installation Failed" - "ExitType" = $SC.ExitTypes.D - } - "L" = @{ - "ExitResult" = "Agent Removal Failed" - "ExitType" = $SC.ExitTypes.D - } - "M" = @{ - "ExitResult" = "No Installation Methods Remaining" - "ExitType" = $SC.ExitTypes.D - } - "AA" = @{ - "ExitMessage" = "An invalid Parameter value or type was provided to a Script Function." - "ExitResult" = "Invalid Parameter" - "ExitType" = $SC.ExitTypes.E - } - "AB" = @{ - "ExitMessage" = ("The current " + $NC.Products.Agent.Name + " installation requires repair, but no Repairs were selected to be applied.") - "ExitResult" = "No Repairs Selected" - "ExitType" = $SC.ExitTypes.E - } - "AC" = @{ - "ExitMessage" = "An error occurred during a file transfer and the Script cannot proceed." - "ExitResult" = "File Transfer Failed" - "ExitType" = $SC.ExitTypes.E - } - "AD" = @{ - "ExitMessage" = "The file at the specified path does not exist." - "ExitResult" = "File Not Found" - "ExitType" = $SC.ExitTypes.E - } - "AE" = @{ - "ExitMessage" = "An error occurred during item creation and the Script cannot proceed." - "ExitResult" = "File/Folder Creation Failed" - "ExitType" = $SC.ExitTypes.E - } -} -# Install Constants -$SC.InstallActions = @{ - "A" = "Install New" - "B" = "Upgrade Existing" - "C" = "Replace Existing" -} -$SC.InstallMethods = @{ - "Attempts" = @{ - "A" = 1 - "B" = 2 - "C" = 1 - "D" = 2 - "E" = 3 - "F" = 3 - } - "Names" = @{ - "A" = "Activation Key (Existing Installation)" - "B" = "Customer/Site ID (Existing Installation)" - "C" = "Activation Key (Historical Installation)" - "D" = "Customer/Site ID (Historical Installation)" - "E" = "Customer/Site ID (Current Script)" - "F" = "Customer/Site ID (Last Successful Script)" - } -} -# Name Constants -$SC.Names = @{ - "HistoryFile" = "AgentHistory.xml" - "Launcher" = "LaunchInstaller" - "LauncherFile" = "LaunchInstaller.bat" - "LauncherProduct" = "Agent Setup Launcher" - "LibraryFiles" = @("InstallAgent-Core.psm1") - "PartnerConfig" = "PartnerConfig.xml" - "Script" = "InstallAgent" - "ScriptProduct" = "Agent Setup Script" -} -# Path Constants -$SC.Paths = @{ - "ExecutionKey" = "HKLM:\SOFTWARE\Solarwinds MSP Community" - "ServiceKey" = "HKLM:\SYSTEM\CurrentControlSet\Services" - "TempFolder" = Split-Path $MyInvocation.MyCommand.Path -Parent -} -$SC.Paths.EventServiceKey = @($SC.Paths.ServiceKey, "EventLog") -join '\' -# Repair Constants -$SC.RepairActions = @{ - "A" = "Install" - "B" = "RestartServices" -} -$SC.Repairs = @{ - "PostRepair" = @{ - "Name" = "Post-Repair Actions" - } - "Recovery" = @{ - "Name" = "Recovery Actions" - } - "A" = @{ - "Name" = "Fix - Orphaned Appliance" - "PostRepairAction" = $SC.RepairActions.B - "RecoveryAction" = $null - } - "B" = @{ - "Name" = "Fix - Incorrect Service Startup Type" - "PostRepairAction" = $null - "RecoveryAction" = $SC.RepairActions.A - } - "C" = @{ - "Name" = "Fix - Incorrect Service Behavior" - "PostRepairAction" = $null - "RecoveryAction" = $SC.RepairActions.A - } - "D" = @{ - "Name" = "Fix - Process/Service Not Running" - "PostRepairAction" = $null - "RecoveryAction" = $SC.RepairActions.A - } -} -# Sequence Constants -$SC.SequenceMessages = @{ - "A" = $null - "B" = "Validating execution requirements..." - "C" = "Diagnosing existing Agent Installation..." - "D" = "Selecting and performing applicable repairs..." - "E" = "Checking installation requirements..." -} -$SC.SequenceNames = @{ - "A" = "Launcher" - "B" = "Validation" - "C" = "Diagnosis" - "D" = "Repair" - "E" = "Installation" -} -$SC.SequenceStatus = @{ - "A" = "COMPLETE" - "B" = "EXITED" - "C" = "IN PROGRESS" - "D" = "SKIPPED" - "E" = "ABORTED" - "F" = "FAILED" -} -# Validation Constants -$SC.Validation = @{ - "Docs" = @{ - $NC.Products.Agent.ApplianceConfig = "Appliance" - $NC.Products.Agent.InstallLog = "Appliance" - $NC.Products.Agent.ServerConfig = "Appliance" - $SC.Names.HistoryFile = "History" - "Registry" = "Registry" - } - "FileNameEXE" = '^((?![<>:"/\\|?*]).)+\.[Ee][Xx][Ee]$' - "FileNameXML" = '^((?![<>:"/\\|?*]).)+\.[Xx][Mm][Ll]$' - "InternalErrorCode" = '^1[0-9]{2}$' - "ItemName" = '^((?![<>:"/\\|?*]).)+$' - "LocalFilePathXML" = '^[a-zA-Z]:\\([^ <>:"/\\|?*]((?![<>:"/\\|?*]).)+((?:"/\\|?*]((?![<>:"/\\|?*]).)+((?:"/\\|?*]((?![<>:"/\\|?*]).)+((?$null | - Select-Object -ExpandProperty ScriptResult 2>$null - ) -eq $SC.InitialScriptResult - ) -and - ( - ( - Get-Date ( - Get-ItemProperty $Script.Results.ScriptKey 2>$null | - Select-Object -ExpandProperty ScriptLastRan 2>$null - ) - ) -gt - (Get-Date).AddMinutes(-($SC.RunningInstanceTimeout)) - ) -) -{ # Another Script is in Progress - # Create a New Key for the Event Source if Required - if ((Test-Path $Script.Results.ScriptEventKey) -eq $false) - { New-EventLog -Source $Script.Results.ScriptSource -LogName $Script.Results.EventLog } - # Write the Event - $Message = ( - "Another Instance of the " + $SC.Names.ScriptProduct + " is currently in progress. " + - "Please review the status of the current Instance by opening the Registry to [" + - $Script.Results.ScriptKey + "].`n" - ) - Write-EventLog -LogName $Script.Results.EventLog -Source $Script.Results.ScriptSource -EventID 9999 -EntryType "Error" -Message $Message -Category 0 - # Cleanup Working Folder - Remove-Item $Script.Path.TempFolder -Force -Recurse 2>$null - exit -} -### Write Registry Values for Script Startup -# Create Script Execution Key if Required -if ((Test-Path $Script.Results.ScriptKey) -eq $false) -{ New-Item $Script.Results.ScriptKey -Force >$null } -else -{ # Remove Sequence Data from Previous Run - Get-ChildItem $Script.Results.ScriptKey | Remove-Item -Force - # Remove Transient Properties from Previous Run - Get-ItemProperty $Script.Results.ScriptKey 2>$null | - Get-Member -MemberType NoteProperty | - Where-Object { $_.Name -match '^Script' } | - ForEach-Object { Remove-ItemProperty $Script.Results.ScriptKey -Name $_.Name -Force } -} -# Update Execution Properties -$Script.Execution.Keys | -ForEach-Object { New-ItemProperty -Path $Script.Results.ScriptKey -Name $_ -Value $Script.Execution.$_ -Force >$null } -### Import Library Items -$SC.Names.LibraryFiles | -ForEach-Object { - $ModuleName = $_ - try - { Import-Module $(@($Script.Path.Library, $ModuleName) -join '\') -ErrorAction Stop } - catch - { # Get the Exception Info - $ExceptionInfo = $_.Exception - # Create a New Key for the Event Source if Required - $Script.Execution.LastResult = $SC.ErrorScriptResult - if ((Test-Path $Script.Results.ScriptEventKey) -eq $false) - { New-EventLog -Source $Script.Results.ScriptSource -LogName $Script.Results.EventLog } - # Write the Event - $Message = ( - "The Function Library for the " + $SC.Names.ScriptProduct + " is either missing or corrupt. " + - "Please verify " + $ModuleName + " exists in the [" + $Script.Path.Library + "] folder, or restore the file to its original state.`n" - ) - Write-EventLog -LogName $Script.Results.EventLog -Source $Script.Results.ScriptSource -EventID 9999 -EntryType "Error" -Message $Message -Category 0 - # Update Execution Properties - $Script.Execution.Keys | - ForEach-Object { New-ItemProperty -Path $Script.Results.ScriptKey -Name $_ -Value $Script.Execution.$_ -Force >$null } - # Cleanup Working Folder - Remove-Item $Script.Path.TempFolder -Force -Recurse 2>$null - exit - } -} -### Import Partner Configuration -try -{ [Xml] $Partner = Get-Content $Script.Path.PartnerFile 2>&1 -ErrorAction Stop } -catch -{ - $ExceptionInfo = $_.Exception - $InvocationInfo = $_.InvocationInfo - CatchError 1 ("Unable to read Partner Configuration at [" + $Script.Path.PartnerFile + "]") -Exit -} -### Get Local Device Info -GetDeviceInfo -### Populate Agent Log Paths using Discovered Device Info -$Agent.Path = @{ - "Checker" = @($Device.PF32, $NC.Paths.BinFolder, $NC.Products.Agent.InstallLog) -join '\' - "ApplianceConfig" = @($Device.PF32, $NC.Paths.ConfigFolder, $NC.Products.Agent.ApplianceConfig) -join '\' - "ApplianceConfigBackup" = @($Device.PF32, $NC.Paths.ConfigFolder, $NC.Products.Agent.ApplianceConfigBackup) -join '\' - "ServerConfig" = @($Device.PF32, $NC.Paths.ConfigFolder, $NC.Products.Agent.ServerConfig) -join '\' - "ServerConfigBackup" = @($Device.PF32, $NC.Paths.ConfigFolder, $NC.Products.Agent.ServerConfigBackup) -join '\' -} -### Elevate Privilege and Re-Run if Required -SelfElevate - -######################################### -########## Main Execution Body ########## -######################################### - -try -{ ### VALIDATION SEQUENCE - ################################################################## - Log -BeginSequence -Message $SC.SequenceMessages.B -Sequence $SC.SequenceNames.B - # Validate Partner Configuration Values - ValidatePartnerConfig - # Validate Execution Mode - ValidateExecution - Log -EndSequence - ################################################################## - ### - - ### DIAGNOSIS SEQUENCE - ################################################################## - Log -BeginSequence -Message $SC.SequenceMessages.C -Sequence $SC.SequenceNames.C - # Diagnose Current Installation Status - DiagnoseAgent - Log -EndSequence - ################################################################## - ### - - ### REPAIR SEQUENCE - ################################################################## - Log -BeginSequence -Message $SC.SequenceMessages.D -Sequence $SC.SequenceNames.D - # Repair the Current Installation - RepairAgent - Log -EndSequence - ################################################################## - ### - - ### INSTALL SEQUENCE - ################################################################## - Log -BeginSequence -Message $SC.SequenceMessages.E -Sequence $SC.SequenceNames.E - # Verify Install Prerequisites - VerifyPrerequisites - # Replace/Upgrade or Install a New Agent - InstallAgent - Log -EndSequence - ################################################################## - ### -} -catch -{ # Terminate Abnormally (Undocumented Error Occurred) - $ExceptionInfo = $_.Exception - $InvocationInfo = $_.InvocationInfo - CatchError -Exit -} - -# Terminate Successfully -Quit 0 \ No newline at end of file diff --git a/Deployment Package/AGENT/LaunchInstaller.bat b/Deployment Package/AGENT/LaunchInstaller.bat deleted file mode 100644 index 0019fdd..0000000 --- a/Deployment Package/AGENT/LaunchInstaller.bat +++ /dev/null @@ -1,477 +0,0 @@ -@ECHO OFF -SETLOCAL EnableDelayedExpansion -SET NL=^ - - -REM = ### ABOUT -REM - Agent Setup Launcher -REM by Ryan Crowther Jr, RADCOMP Technologies - 2019-08-26 -REM - Original Script (InstallAgent.vbs) by Tim Wiser, GCI Managed IT - 2015-03 - -REM = ### USAGE -REM - This Launcher should ideally be called by a Group Policy with the -REM client's Customer-Level N-Central ID as the only Parameter, but may -REM also be run On-Demand from another local or network location, using -REM the same argument. See the README.md for detailed Deployment Steps. - -REM = ### KNOWN ISSUES -REM - WIC Installation Method not yet implemented, this affects: -REM -- Confirmed - Windows XP 64-bit (WIC must be installed manually after Service Pack 2 is installed) -REM -- Untested - Windows Server 2003 (same behavior expected, since XP-64 was based on this Build) - -REM = ### USER DEFINITIONS - Feel free to change these -REM - Working Folder -SET TempFolder=C:\Windows\Temp\AGPO -REM - Maximum Download Attempts (per File) -SET DLThreshold=3 - -REM = ### DOWNLOAD SOURCES - May require updating as Links change/break -REM - # Service Packs -REM - Windows Vista and Server 2008 SP1 -SET "SP1_Vista-x86=http://www.download.windowsupdate.com/msdownload/update/software/svpk/2008/04/windows6.0-kb936330-x86_b8a3fa8f819269e37d8acde799e7a9aea3dd4529.exe" -SET "SP1_Vista-x64=http://www.download.windowsupdate.com/msdownload/update/software/svpk/2008/04/windows6.0-kb936330-x64_12eed6cf0a842ce2a609c622b843afc289a8f4b9.exe" -REM - Windows XP 64-bit and Server 2003 SP2 -SET "SP2_2003-x86=http://www.download.windowsupdate.com/msdownload/update/software/dflt/2008/02/windowsserver2003-kb914961-sp2-x86-enu_51e1759a1fda6cd588660324abaed59dd3bbe86b.exe" -SET "SP2_2003-x64=http://www.download.windowsupdate.com/msdownload/update/v3-19990518/cabpool/windowsserver2003.windowsxp-kb914961-sp2-x64-enu_7f8e909c52d23ac8b5dbfd73f1f12d3ee0fe794c.exe" -REM - Windows XP SP3 -SET "SP3_XP-x86=http://www.download.windowsupdate.com/msdownload/update/software/dflt/2008/04/windowsxp-kb936929-sp3-x86-enu_c81472f7eeea2eca421e116cd4c03e2300ebfde4.exe" -REM - # .NET Framework 2.0 SP1 -SET "NET2SP1=http://www.download.windowsupdate.com/msdownload/update/software/svpk/2008/01/netfx20sp1_x86_eef5a36924cdf0c02598ccf96aa4f60887a49840.exe" -REM - # PowerShell 2.0 -REM - Windows Vista and Server 2008 -SET "PS2_Vista_x86=http://download.windowsupdate.com/msdownload/update/software/updt/2011/02/windows6.0-kb968930-x86_16fd2e93be2e7265821191119ddfc0cdaa6f4243.msu" -SET "PS2_Vista-x64=http://download.windowsupdate.com/msdownload/update/software/updt/2011/02/windows6.0-kb968930-x64_4de013d593181a2a04217ce3b0e7536ab56995aa.msu" -REM - Windows XP 64-bit and Server 2003 -SET "PS2_2003-x86=http://download.windowsupdate.com/msdownload/update/software/updt/2009/11/windowsserver2003-kb968930-x86-eng_843dca5f32b47a3bc36cb4d7f3a92dfd2fcdddb3.exe" -SET "PS2_2003-x64=http://download.windowsupdate.com/msdownload/update/software/updt/2009/11/windowsserver2003-kb968930-x64-eng_8ba702aa016e4c5aed581814647f4d55635eff5c.exe" -REM - Windows XP SP3 -SET "PS2_XP-x86=http://download.windowsupdate.com/msdownload/update/software/updt/2009/11/windowsxp-kb968930-x86-eng_540d661066953d76a6907b6ee0d1cd4531c1e1c6.exe" - -REM = ### DEFINITIONS -REM - Launcher Script Name -SET LauncherScript=Agent Setup Launcher -REM - Setup Script Name -SET SetupScript=Agent Setup Script -REM - Default Customer ID -SET CustomerID=%1% -REM - Working Library Folder -SET LibFolder=%TempFolder%\Lib -REM - Deployment Folder -SET DeployFolder=%~dp0 -SET DeployLib=%DeployFolder%Lib -REM - OS Display Name -FOR /F "DELIMS=|" %%A IN ('WMIC OS GET NAME ^| FIND "Windows"') DO SET OSCaption=%%A -ECHO "%OSCaption%" | FIND "Server" >NUL -REM - Server OS Type -IF %ERRORLEVEL% EQU 0 (SET OSType=Server) -REM - OS Build Number -FOR /F "TOKENS=2 DELIMS=[]" %%A IN ('VER') DO SET OSBuild=%%A -SET OSBuild=%OSBuild:~8% -REM - OS Architecture -ECHO %PROCESSOR_ARCHITECTURE% | FIND "64" >NUL -IF %ERRORLEVEL% EQU 0 (SET OSArch=x64) ELSE (SET OSArch=x86) -REM - Program Files Folder -IF "%OSArch%" EQU "x64" (SET "PF32=%SYSTEMDRIVE%\Program Files (x86)") -IF "%OSArch%" EQU "x86" (SET "PF32=%SYSTEMDRIVE%\Program Files") - -REM = ### BODY -ECHO == Launcher Started == - -:CheckOSRequirements -REM = Check for OS that may Require PowerShell 2.0 Installation -REM - Windows 10 -IF "%OSBuild:~0,3%" EQU "10." ( - IF %OSBuild:~3,1% EQU 0 (GOTO LaunchScript) -) -REM - Windows 7/8/8.1 and Server 2008 R2/2012/2012 R2 -IF "%OSBuild:~0,2%" EQU "6." ( - IF %OSBuild:~2,1% GTR 0 (GOTO LaunchScript) -) -REM - Windows Vista and Server 2008 -IF "%OSBuild:~0,3%" EQU "6.0" (SET OSLevel=Vista) -REM - Windows XP x64 and Server 2003 -IF "%OSBuild:~0,3%" EQU "5.2" (SET OSLevel=2003) -REM - Windows XP -IF "%OSBuild:~0,3%" EQU "5.1" (SET OSLevel=XP) -REM - Older Versions (NT and Below) -IF "%OSBuild:~0,3%" EQU "5.0" (GOTO QuitIncompatible) -IF %OSBuild:~0,1% LSS 5 (GOTO QuitIncompatible) - -:CheckPSVersion -REM - Verify PowerShell Installation -REG QUERY "HKLM\SOFTWARE\Microsoft\PowerShell\1" /v Install 2>NUL | FIND "Install" >NUL -IF %ERRORLEVEL% EQU 0 ( - FOR /F "TOKENS=3" %%A IN ('REG QUERY "HKLM\SOFTWARE\Microsoft\PowerShell\1" /v Install ^| FIND "Install"') DO SET PSInstalled=%%A -) -IF "%PSInstalled%" EQU "0x1" ( - REM - Get PowerShell Version - FOR /F "TOKENS=3" %%A IN ('REG QUERY "HKLM\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine" /v PowerShellVersion ^| FIND "PowerShellVersion"') DO SET PSVersion=%%A -) -IF "%PSVersion%" EQU "2.0" (GOTO LaunchScript) - -:CheckServicePack -REM - Create Local Working Folder -IF EXIST "%TempFolder%\*" (SET PathType=Directory) -IF EXIST "%TempFolder%" ( - IF "%PathType%" NEQ "Directory" ( - DEL /Q "%TempFolder%" - MKDIR "%TempFolder%" - ) -) ELSE (MKDIR "%TempFolder%") -SET PathType= -IF EXIST "%LibFolder%\*" (SET PathType=Directory) -IF EXIST "%LibFolder%" ( - IF "%PathType%" NEQ "Directory" ( - DEL /Q "%LibFolder%" - MKDIR "%LibFolder%" - ) -) ELSE (MKDIR "%LibFolder%") -SET PathType= -REM - Verify Windows Update Service is Enabled -SC CONFIG wuauserv start= delayed-auto >NUL -REM - Get Current Service Pack -SET ServicePack=0 -REG QUERY "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v CSDVersion >NUL 2>NUL -IF %ERRORLEVEL% EQU 0 ( - FOR /F "TOKENS=5" %%A IN ('REG QUERY "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v CSDVersion ^| FIND "Service Pack"') DO SET ServicePack=%%A -) - -:SetDownloadInfo -REM - Set Required Service Packs and Download URLs -SET "NETurl=%NET2SP1%" -REM - Windows Vista and Server 2008 -IF "%OSLevel%" EQU "Vista" ( - IF "%OSArch%" EQU "x64" ( - SET "SPurl=%SP1_Vista-x64%" - SET "PSurl=%PS2_Vista-x64%" - ) ELSE ( - SET "SPurl=%SP1_Vista-x86%" - SET "PSurl=%PS2_Vista-x86%" - ) - SET RequiredPack=1 -) -REM - Windows XP 64-bit and Server 2003 -IF "%OSLevel%" EQU "2003" ( - IF "%OSArch%" EQU "x64" ( - SET "SPurl=%SP2_2003-x64%" - SET "PSurl=%PS2_2003-x64%" - ) ELSE ( - SET "SPurl=%SP2_2003-x86%" - SET "PSurl=%PS2_2003-x86%" - ) - SET RequiredPack=2 -) -REM - Windows XP -IF "%OSLevel%" EQU "XP" ( - SET "SPurl=%SP3_XP-x86%" - SET "PSurl=%PS2_XP-x86%" - SET RequiredPack=3 -) -IF %ServicePack% GEQ %RequiredPack% (GOTO CheckNETFramework) - -:CheckSupportTools -REM - Verify Service Pack 1 for XP 64-bit and Server 2003 -IF "%OSBuild:~0,3%" EQU "5.2" ( - IF %ServicePack% LSS 1 ( - SET "Message=Service Pack 1 for %OSCaption% is missing and must be installed manually in order for the Launcher to continue." - GOTO QuitFailure - ) -) -REM - Verify Service Pack 2 for XP -IF "%OSBuild:~0,3%" EQU "5.1" ( - IF %ServicePack% LSS 2 ( - SET "Message=Service Pack 2 for %OSCaption% is missing and must be installed manually in order for the Launcher to continue." - GOTO QuitFailure - ) -) - -:InstallSupportTools -REM - Verify Tools aren't Already Present -SET "BITS=%PF32%\Support Tools\BITSADMIN.EXE" -SET "ToolsPath=%DeployFolder%PS2Install\XPTools\%OSLevel%\*" -SET ToolsFile=suptools.msi -IF NOT EXIST "%BITS%" ( - REM - Fetch Support Tools Installer - COPY /Y "%ToolsPath%" "%TempFolder%" >NUL - REM - Install Windows Support Tools - IF %ERRORLEVEL% EQU 0 ( - ECHO - Installing Windows Support Tools for %OSCaption%... - START "" /WAIT "%WINDIR%\SYSTEM32\MSIEXEC.EXE" /I "%TempFolder%\%ToolsFile%" /QN - ) ELSE ( - SET "Message=The Windows Support Tools installer was missing or not found after transfer." - GOTO QuitFailure - ) - IF %ERRORLEVEL% NEQ 0 ( - SET "Message=Support Tools Installation Failed - Error %ERRORLEVEL%" - GOTO QuitFailure - ) -) - -:GetServicePack -REM - Get Appropriate Service Pack for Install -SET "SPFile=SP%RequiredPack%_%OSLevel%-%OSArch%.exe" -REM - Fetch Service Pack Installer -IF EXIST "%DeployFolder%PS2Install\Cache\ServicePacks\%SPFile%" ( - REM - Copy from Deployment Folder - COPY /Y "%DeployFolder%PS2Install\Cache\ServicePacks\%SPFile%" "%TempFolder%" >NUL - GOTO InstallServicePack -) -SET Attempts=1 -GOTO DownloadServicePack - -:RetryDownloadServicePack -REM - Cancel the Download Job -"%BITS%" /CANCEL "SPFetch" >NUL -SET /A "Attempts = Attempts + 1" -REM - Wait Between Each Attempt -PING 192.0.2.1 -n 1 -w 30000 >NUL - -:DownloadServicePack -REM - Download from the Web -START "" "%BITS%" /TRANSFER "SPFetch" "%SPurl%" "%TempFolder%\%SPFile%" - -:whileServicePackDownloading -REM - Exit After too many Unsuccessful Attempts -IF %Attempts% GEQ %DLThreshold% ( - SET "Message=Multiple attempts to download Service Pack %RequiredPack% for %OSCaption% have failed. Consider installing the Package manually to proceed." - GOTO QuitFailure -) -REM - Retrieve the Download Status -"%BITS%" /RAWRETURN /GETSTATE "SPFetch" >NUL 2>NUL -IF %ERRORLEVEL% EQU 0 ( - FOR /F %%A IN ('CALL "%BITS%" /RAWRETURN /GETSTATE "SPFetch"') DO SET DLStatus=%%A - REM - Wait Between Each Check - PING 192.0.2.1 -n 1 -w 10000 >NUL -) ELSE ( - REM - Download has Completed - GOTO InstallServicePack -) -REM - Cancel and Retry Download if an Error Occurs -IF "%DLStatus%" EQU "ERROR" (GOTO RetryDownloadServicePack) -IF "%DLStatus%" EQU "TRANSIENT_ERROR" (GOTO RetryDownloadServicePack) -GOTO whileServicePackDownloading - -:InstallServicePack -SET Attempts= -SET DLStatus= -REM - Install Service Pack -IF EXIST "%TempFolder%\%SPFile%" ( - ECHO - Installing Service Pack %RequiredPack% for %OSCaption%... - START "" /WAIT "%TempFolder%\%SPFile%" /quiet /norestart -) ELSE ( - SET "Message=Required Service Pack installer was missing or not found after transfer. (SP%RequiredPack% for %OSCaption%)" - GOTO QuitFailure -) -IF %ERRORLEVEL% NEQ 0 ( - IF %ERRORLEVEL% EQU 3010 ( - SET "Installed=Service Pack %RequiredPack% for %OSCaption%" - GOTO QuitRestart - ) - SET "Message=Service Pack Installation Failed - Error %ERRORLEVEL%" - GOTO QuitFailure -) ELSE ( - SET "Installed=Service Pack %RequiredPack% for %OSCaption%" - GOTO QuitRestart -) - -:CheckNETFramework -REM - Check Current .NET Framework -REG QUERY "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" /v SP 2>NUL | FIND "SP" >NUL -IF %ERRORLEVEL% EQU 0 ( - FOR /F "TOKENS=3" %%A IN ('REG QUERY "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" /v SP ^| FIND "SP"') DO SET NETInstalled=%%A -) -IF "%NETInstalled:~2,1%" GTR 0 (GOTO InstallPS2) - -:GetNET2SP1 -REM - Get Appropriate Service Pack for Install -SET "NETFile=NetFx20SP1_x86.exe" -REM - Fetch .NET Framework Installer -IF EXIST "%DeployFolder%PS2Install\Cache\NET\%NETFile%" ( - REM - Copy from Deployment Folder - COPY /Y "%DeployFolder%PS2Install\Cache\NET\%NETFile%" "%TempFolder%" >NUL - GOTO InstallNET2SP1 -) -SET Attempts=1 -GOTO DownloadNET2SP1 - -:RetryDownloadNET2SP1 -REM - Cancel the Download Job -"%BITS%" /CANCEL "NETFetch" >NUL -SET /A "Attempts = Attempts + 1" -REM - Wait Between Each Attempt -PING 192.0.2.1 -n 1 -w 30000 >NUL - -:DownloadNET2SP1 -REM - Download from the Web -START "%BITS%" /TRANSFER "NETFetch" "%NETurl%" "%TempFolder%\%NETFile%" - -:whileNETDownloading -REM - Exit After too many Unsuccessful Attempts -IF %Attempts% GEQ %DLThreshold% ( - SET "Message=Multiple attempts to download .NET Framework 2.0 SP1 have failed. Consider installing the Package manually to proceed." - GOTO QuitFailure -) -REM - Retrieve the Download Status -"%BITS%" /RAWRETURN /GETSTATE "NETFetch" >NUL 2>NUL -IF %ERRORLEVEL% EQU 0 ( - FOR /F %%A IN ('CALL "%BITS%" /RAWRETURN /GETSTATE "NETFetch"') DO SET DLStatus=%%A - REM - Wait Between Each Check - PING 192.0.2.1 -n 1 -w 10000 >NUL -) ELSE ( - REM - Download has Completed - GOTO InstallNET2SP1 -) -REM - Cancel and Retry Download if an Error Occurs -IF "%DLStatus%" EQU "ERROR" (GOTO RetryDownloadNET2SP1) -IF "%DLStatus%" EQU "TRANSIENT_ERROR" (GOTO RetryDownloadNET2SP1) -GOTO whileNETDownloading - -:InstallNET2SP1 -SET Attempts= -SET DLStatus= -REM - Install .NET Framework -IF EXIST "%TempFolder%\%NETFile%" ( - ECHO - Installing .NET Framework 2.0 SP1... - START "" /WAIT "%TempFolder%\%NETFile%" /q /norestart -) ELSE ( - SET "Message=.NET Framework 2.0 SP1 Installer was missing or not found after transfer." - GOTO QuitFailure -) -IF %ERRORLEVEL% NEQ 0 ( - SET "Message=.NET Framework 2.0 SP1 Installation Failed - Error %ERRORLEVEL%" - GOTO QuitFailure -) - -:GetPS2 -REM - Set Appropriate Update Package Extension for Install -IF "%OSLevel%" EQU "Vista" ( - SET "PSFile=PS2_%OSLevel%-%OSArch%.msu" -) ELSE ( - SET "PSFile=PS2_%OSLevel%-%OSArch%.exe" -) -REM - Fetch PowerShell 2.0 Installer -IF EXIST "%DeployFolder%PS2Install\Cache\PowerShell\%PSFile%" ( - REM - Copy from Deployment Folder - COPY /Y "%DeployFolder%PS2Install\Cache\PowerShell\%PSFile%" "%TempFolder%" >NUL - GOTO InstallPS2 -) -SET Attempts=1 -GOTO DownloadPS2 - -:RetryDownloadPS2 -REM - Cancel the Download Job -"%BITS%" /CANCEL "PSFetch" >NUL -SET /A "Attempts = Attempts + 1" -REM - Wait Between Each Attempt -PING 192.0.2.1 -n 1 -w 30000 >NUL - -:DownloadPS2 -REM - Download from the Web -START "" "%BITS%" /TRANSFER "PSFetch" "%PSurl%" "%TempFolder%\%PSFile%" - -:whilePS2Downloading -REM - Exit After too many Unsuccessful Attempts -IF %Attempts% GEQ %DLThreshold% ( - SET "Message=Multiple attempts to download PowerShell 2.0 have failed. Consider installing the Package manually to proceed." - GOTO QuitFailure -) -REM - Retrieve the Download Status -"%BITS%" /RAWRETURN /GETSTATE "PSFetch" >NUL 2>NUL -IF %ERRORLEVEL% EQU 0 ( - FOR /F %%A IN ('CALL "%BITS%" /RAWRETURN /GETSTATE "PSFetch"') DO SET DLStatus=%%A - REM - Wait Between Each Check - PING 192.0.2.1 -n 1 -w 10000 >NUL -) ELSE ( - REM - Download has Completed - GOTO InstallPS2 -) -REM - Cancel and Retry Download if an Error Occurs -IF "%DLStatus%" EQU "ERROR" (GOTO RetryDownloadPS2) -IF "%DLStatus%" EQU "TRANSIENT_ERROR" (GOTO RetryDownloadPS2) -REM - Continue Monitoring the Download -GOTO whilePS2Downloading - -:InstallPS2 -SET Attempts= -SET DLStatus= -REM - Install PowerShell 2.0 -IF EXIST "%TempFolder%\%PSFile%" ( - ECHO - Installing PowerShell 2.0 for %OSCaption%... - START "" /WAIT "%TempFolder%\%PSFile%" /quiet /norestart -) ELSE ( - SET "Message=PowerShell 2.0 Installer was missing or not found after transfer. (%OSCaption%)" - GOTO QuitFailure -) -IF %ERRORLEVEL% NEQ 0 ( - IF %ERRORLEVEL% EQU 3010 ( - SET Installed=PowerShell 2.0 - GOTO QuitRestart - ) - SET "Message=PowerShell 2.0 Installation Failed - Error %ERRORLEVEL%" - GOTO QuitFailure -) ELSE ( - SET Installed=PowerShell 2.0 - GOTO QuitRestart -) - -:LaunchScript -REM - Create Local Working Folder -IF EXIST "%TempFolder%\*" (SET PathType=Directory) -IF EXIST "%TempFolder%" ( - IF "%PathType%" NEQ "Directory" ( - DEL /Q "%TempFolder%" - MKDIR "%TempFolder%" - ) -) ELSE (MKDIR "%TempFolder%") -SET PathType= -IF EXIST "%LibFolder%\*" (SET PathType=Directory) -IF EXIST "%LibFolder%" ( - IF "%PathType%" NEQ "Directory" ( - DEL /Q "%LibFolder%" - MKDIR "%LibFolder%" - ) -) ELSE (MKDIR "%LibFolder%") -SET PathType= -REM - Fetch Script Items -COPY /Y "%DeployFolder%*" "%TempFolder%" >NUL -COPY /Y "%DeployLib%\*" "%LibFolder%" >NUL -IF %ERRORLEVEL% EQU 0 ( - REM - Launch Agent Setup Script - IF "%CustomerID%" NEQ "" ( - START "" %WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File "%TempFolder%\InstallAgent.ps1" -CustomerID %CustomerID% -LauncherPath "%DeployFolder% - GOTO QuitSuccess - ) ELSE ( - START "" %WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File "%TempFolder%\InstallAgent.ps1" -LauncherPath "%DeployFolder% - GOTO QuitSuccess - ) -) ELSE ( - SET "Message=%SetupScript% was missing or not found after transfer." - GOTO QuitFailure -) - -:QuitIncompatible -ECHO X OS Not Compatible with either the Agent or the %SetupScript% -EVENTCREATE /T INFORMATION /ID 13 /L APPLICATION /SO "%LauncherScript%" /D "The OS is not compatible with the N-Central Agent or the %SetupScript%." >NUL -GOTO Done - -:QuitFailure -ECHO X Execution Failed - %SetupScript% Not Started (See Application Event Log for Details) -EVENTCREATE /T ERROR /ID 11 /L APPLICATION /SO "%LauncherScript%" /D "!Message!" >NUL -GOTO Cleanup - -:QuitRestart -ECHO ! Reboot Required for Prerequisite Installation - Please Re-run this Script after a Reboot -EVENTCREATE /T WARNING /ID 12 /L APPLICATION /SO "%LauncherScript%" /D "The system requires a reboot for %Installed%.!NL!!NL!For Group Policy Deployments - The %LauncherScript% will start at next Domain boot.!NL!For On-Demand Deployments - Reboot the Device and re-run the %LauncherScript% to continue." >NUL -GOTO Cleanup - -:QuitSuccess -ECHO O %SetupScript% Launched Successfully -GOTO Done - -:Cleanup -RD /S /Q "%TempFolder%" 2>NUL - -:Done -ECHO == Launcher Finished == -ECHO Exiting... -PING 192.0.2.1 -n 1 -w 10000 >NUL \ No newline at end of file diff --git a/Deployment Package/AGENT/LegacyAgent/NET 4.0 Installer Download.url b/Deployment Package/AGENT/LegacyAgent/NET 4.0 Installer Download.url deleted file mode 100644 index b321117..0000000 --- a/Deployment Package/AGENT/LegacyAgent/NET 4.0 Installer Download.url +++ /dev/null @@ -1,6 +0,0 @@ -[{000214A0-0000-0000-C000-000000000046}] -Prop3=19,11 -[InternetShortcut] -IDList= -URL=https://download.microsoft.com/download/9/5/A/95A9616B-7A37-4AF6-BC36-D6EA96C8DAAE/dotNetFx40_Full_x86_x64.exe -HotKey=0 diff --git a/Deployment Package/AGENT/LegacyAgent/NET4_0-Universal.exe.txt b/Deployment Package/AGENT/LegacyAgent/NET4_0-Universal.exe.txt deleted file mode 100644 index 3470acf..0000000 --- a/Deployment Package/AGENT/LegacyAgent/NET4_0-Universal.exe.txt +++ /dev/null @@ -1,3 +0,0 @@ -- Download the .NET Framework 4.0 Universal Installer from Microsoft by using the provided URL File in this Folder -- Place it here, and call it by the same name of this text file (without the txt, of course!) -- Or, optionally, rename it and update the Property in PartnerConfig.xml! \ No newline at end of file diff --git a/Deployment Package/AGENT/LegacyAgent/WindowsAgentSetup.exe b/Deployment Package/AGENT/LegacyAgent/WindowsAgentSetup.exe deleted file mode 100644 index b679184..0000000 Binary files a/Deployment Package/AGENT/LegacyAgent/WindowsAgentSetup.exe and /dev/null differ diff --git a/Deployment Package/AGENT/Lib/InstallAgent-Core.psm1 b/Deployment Package/AGENT/Lib/InstallAgent-Core.psm1 deleted file mode 100644 index 32ce1cf..0000000 --- a/Deployment Package/AGENT/Lib/InstallAgent-Core.psm1 +++ /dev/null @@ -1,3181 +0,0 @@ -# Core Functions for the Agent Setup Script (InstallAgent.ps1) -# Last Revised: 2019-08-26 -# Module Version: 5.0.1 - -### INITIALIZATION FUNCTIONS -############################### - -function WriteKey -{ ### Parameters - ############################### - param ($Key, $Properties) - ### Function Body - ############################### - # Create the Key if Missing - if ((Test-Path $Key) -eq $false) - { New-Item -Path $Key -Force >$null } - # Add Properties and Assign Values - $Properties.Keys | - ForEach-Object { New-ItemProperty -Path $Key -Name $_ -Value $Properties.$_ -Force >$null } -} - -function AlphaValue -{ ### Parameters - ############################### - param ($Value, [Switch] $2Digit) - ### Function Body - ############################### - if ($2Digit -eq $true) - { return ("A" + [String]([Char]($Value - 35))) } - else - { return ([String]([Char]($Value + 65))) } -} - -function Quit -{ ### Parameters - ############################### - param ($Code) - ### Function Body - ############################### - ### Update the Script Registry Keys - # Assign the Appropriate Error Message - switch ($Code) - { # Successful Execution - 0 - { - $Script.Execution.ScriptAction = $SC.SuccessScriptAction - $LCode = AlphaValue $Code - break - } - # Documented Typical Error - { @(1..25) -contains $_ } - { $LCode = AlphaValue $Code; break } - # Documented Internal Error - { @(100..125) -contains $_ } - { $LCode = AlphaValue $Code -2Digit; break } - # Undocumented Error - Default - { $Code = 999; $LCode = "Error"; break } - } - $Comment = $SC.Exit.$LCode.ExitResult - ### Publish Execution Results to the Registry - # Create a New Registry Key for Script Results and Actions - if ((Test-Path ($Script.Results.ScriptKey)) -eq $false) - { New-Item $($Script.Results.ScriptKey) -Force >$null } - # Collect Final Execution Values - $Script.Execution.ScriptResult = $Comment - $Script.Execution.ScriptExitCode = $Code - # Write Final Execution Values - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Append the Function Info if a Validation Error Occurs - if ($Code -match $SC.Validation.InternalErrorCode) - { - $Script.Results.Function = $Function.Name - $Script.Results.LineNumber = $Function.LineNumber - $Script.Results.Details += @("`n== Error Details ==") - $Script.Results.Details += @($SC.Exit.$LCode.ExitMessage) - $Script.Results.Details += - if ($null -ne $Script.Results.Function) - { @("Function Name: " + $Script.Results.Function) } - $Script.Results.Details += - if ($null -ne $Script.Results.LineNumber) - { @("Called at Line: " + $Script.Results.LineNumber) } - $Script.Results.Details += - if ($null -ne $Script.Results.Parameter) - { @("Parameter Name: " + $Script.Results.Parameter) } - $Script.Results.Details += - if ($null -ne $Script.Results.GivenParameter) - { @("Given Parameter: " + $Script.Results.GivenParameter) } - } - ### Format the Message Data for the Event Log - # Add the Overall Script Result - $Script.Results.EventMessage += @("Overall Script Result: " + $SC.Exit.$LCode.ExitType + "`n") - # Add the Completion Status of Each Sequence - $Script.Sequence.Order | - ForEach-Object { - $Script.Results.EventMessage += @( - $_, - $Script.Sequence.Status[([Array]::IndexOf($Script.Sequence.Order, $_))] - ) -join ' - ' - } - # Add the Detailed Messages for Each Sequence - $Script.Results.EventMessage += @("`n" + ($Script.Results.Details -join "`n")) - # For Typical Errors, Add the Branded Error Contact Message from Partner Configuration - if ( - ($Code -ne 999) -and - ($Code -ne 0) -and - ($null -ne $Config.ErrorContactInfo) - ) - { - $Script.Results.EventMessage += - "`n--=========================--", - "`nTo report this documented issue, please submit this Event Log entry to:`n", - $Config.ErrorContactInfo - } - # Combine All Message Items - $Script.Results.EventMessage = ($Script.Results.EventMessage -join "`n").TrimEnd('') - ### Publish Execution Results to the Event Log - # Create a New Key for the Event Source if Required - if ((Test-Path $Script.Results.ScriptEventKey) -eq $false) - { New-EventLog -Source $Script.Results.ScriptSource -LogName $Script.Results.EventLog } - # Write the Event - Write-EventLog -LogName $Script.Results.EventLog -Source $Script.Results.ScriptSource -EventID (9000 + $Code) -EntryType $Script.Results.ErrorLevel -Message $Script.Results.EventMessage -Category 0 - ### Cleanup Outdated Items - $SC.Paths.Old.Values | - ForEach-Object { Remove-Item $_ -Recurse -Force 2>$null } - ### Cleanup Working Folder - Remove-Item $Script.Path.InstallDrop -Force -Recurse 2>$null - Remove-Item $Script.Path.TempFolder -Force -Recurse 2>$null - exit -} - -function Log -{ ### Parameters - ############################### - param - ( - $EventType, $Code, - $Message, $Sequence, - [Switch] $BeginSequence, - [Switch] $EndSequence, - [Switch] $Exit - ) - ### Parameter Validation - ############################### - if ($EndSequence -eq $true) - { <# Other Parameters are not Required #> } - else - { - if ($BeginSequence -eq $true) - { # EventType and Code are not Required - $EventType = "Information" - $Code = 0 - } - else - { ### EventType - Must be a Valid Full or Partial Event Type - $EventType = - switch ($EventType) - { - { "Information" -like ($_ + "*") } - { "Information"; break } - { "Warning" -like ($_ + "*") } - { "Warning"; break } - { "Error" -like ($_ + "*") } - { "Error"; break } - Default - { $null; break } - } - if ($null -eq $EventType) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "EventType" - $Script.Results.GivenParameter = $EventType - Quit 100 - } - } - ### Message - Must be a String - if ( - ($null -ne $Message) -and - (($Message -is [String]) -or - ($Message -is [Array])) - ) - { - if ($Message -is [Array]) - { $Message = $Message -join "`n" } - } - else - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "Message" - Quit 100 - } - ### Sequence - Must be a String - if ($null -ne $Sequence) - { - if ($Sequence -is [String]) - { $Script.Execution.ScriptSequence = $Sequence } - else - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "Sequence" - $Script.Results.GivenParameter = $Sequence - Quit 100 - } - } - else - { - if ($null -eq $Script.Execution.ScriptSequence) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "Sequence" - $Script.Results.GivenParameter = "None Provided (Required Parameter when there is no Active Sequence)" - Quit 100 - } - } - } - ### Function Body - ############################### - if ($EndSequence -eq $false) - { ### Update Script Event Level - $Script.Results.ErrorLevel = - switch ($Script.Results.ErrorLevel) - { - { $null -eq $_ } - { $EventType; break } - "Information" - { $EventType; break } - "Warning" - { if ($EventType -eq "Error") { $EventType }; break } - } - } - ### Update Script Sequence Results - # Update Sequence Order - if ($Script.Sequence.Order -notcontains $Script.Execution.ScriptSequence) - { $Script.Sequence.Order += @($Script.Execution.ScriptSequence) } - # Determine the Sequence Status - if ($BeginSequence -eq $true) - { - $Status = $SC.SequenceStatus.C - # Add Sequence Header Before Detail Message - $Script.Results.Details += - ("--== " + $Script.Execution.ScriptSequence + " ==--"), - $Message - } - if (($BeginSequence -eq $false) -and ($EndSequence -eq $false)) - { - $Status = - switch ($Code) - { - 0 - { $Script.Sequence.Status[-1]; break } - { $null -eq $_ } - { ($SC.SequenceStatus.B + " (UNKNOWN)"); break } - Default - { ($SC.SequenceStatus.B + " ($Code)"); break } - } - # Add Detail Message to Current Sequence - $Script.Results.Details += @("`n" + $Message) - } - if ($EndSequence -eq $true) - { # Change Status to COMPLETE Unless Otherwise Specified - $Status = - if ($Script.Sequence.Status[-1] -eq $SC.SequenceStatus.C) - { $SC.SequenceStatus.A } else { $Script.Sequence.Status[-1] } - # Add Sequence Footer After Detail Message - $Script.Results.Details += @("--== " + $Script.Execution.ScriptSequence + " Finished ==--`n") - } - # Update the Event Log Sequence Status - $SelectedStatus = [Array]::IndexOf($Script.Sequence.Order, $Sequence) - switch (($Script.Sequence.Status).Count) - { - { $_ -le $SelectedStatus } - { $Script.Sequence.Status += @($Status) } - Default - { ($Script.Sequence.Status)[$SelectedStatus] = $Status } - } - # Update the Registry Sequence Status if Required - if (@($BeginSequence, $EndSequence) -contains $true) - { WriteKey $Script.Results.ScriptKey $Script.Execution } - ### Terminate if Requested - if ($Exit -eq $true) { Quit $Code } -} - -function CatchError -{ ### Parameters - ############################### - param ($Code, $Message, [Switch] $Exit) - ### Function Body - ############################### - # Add a Message if Found - if ($null -ne $Message) - { $Out = @($Message) } - else - { $Out = @("The Script encountered an undocumented error.") } - # Get Any Exception Info - if ($null -ne $ExceptionInfo) - { - if ($null -ne $InvocationInfo.Line) - { - $ExcCmd = ($InvocationInfo.Line).Replace("-ErrorAction Stop", "").Trim() - $ExcCmdLN = $InvocationInfo.ScriptLineNumber - } - $ExcLookup = $ExceptionInfo.InnerException - do - { - $ExcMsg += @($ExcLookup.Message) - $ExcLookup = $ExcLookup.InnerException - } while ($null -ne $ExcLookup) - $ExcMsg | - ForEach-Object { if ($null -ne $_) { $ExcMsgItems++ } } - if (-not ($ExcMsgItems -gt 0)) - { $ExcMsg = @($ExceptionInfo.Message) } - if (($null -ne $ExcCmd) -and ($null -ne $ExcCmdLN)) - { - $Out += - "== Command Details ==", - ("Faulting Line Number: " + $ExcCmdLN), - ("Faulting Command: " + $ExcCmd + "`n") - } - $Out += - "== Error Message ==", - ($ExcMsg -join "`n") - } - Log E $Code $Out - if ($Exit -eq $true) { Quit $Code } -} - -function GetDeviceInfo -{ ### Function Body - ############################### - ### Computer Details - $WMIinfo = Get-WmiObject Win32_ComputerSystem - $WMIos = Get-WmiObject Win32_OperatingSystem - # Device Name - [String] $Device.Hostname = & "$env:windir\SYSTEM32\HOSTNAME.EXE" - [String] $Device.Name = $WMIinfo.Name - # Domain Role - [String] $Device.FQDN = $WMIinfo.Domain - $Device_Flags = - "Role", "IsWKST", "IsSRV", - "IsDomainJoined", "IsDC", "IsBDC" - $Device_Values = - switch ($WMIinfo.DomainRole) - { - 0 - { @("Standalone Workstation", $true, $false, $false, $false, $false); break } - 1 - { @("Domain/Member Workstation", $true, $false, $true, $false, $false); break } - 2 - { @("Standalone Server", $false, $true, $false, $false, $false); break } - 3 - { @("Domain/Member Server", $false, $true, $true, $false, $false); break } - 4 - { @("Domain Controller (DC)", $false, $true, $true, $true, $false); break } - 5 - { @("Baseline Domain Controller (BDC)", $false, $true, $true, $true, $true); break } - Default - { $null; break } - } - if ($null -ne $Device_Values) - { - for ($i = 0; $i -lt $Device_Flags.Count; $i++) - { $Device.($Device_Flags[$i]) = $Device_Values[$i] } - } - # Last Boot Time - [DateTime] $Device.LastBootTime = - if ($null -ne $WMIos.LastBootUpTime) - { Get-Date ($WMIos.ConvertToDateTime($WMIos.LastBootUpTime)) -UFormat "%Y-%m-%d %r" } else { 0 } - ### OS/Software Details - # PowerShell Version - [Version] $Device.PSVersion = - if ($null -ne $PSVersionTable) - { $PSVersionTable.PSVersion } else { "1.0" } - # Operating System Name/Build - [String] $Device.OSName = ($WMIos.Caption).Trim().Replace("Microsoftr", "Microsoft").Replace("Serverr", "Server").Replace("Windowsr", "Windows") - [Version] $Device.OSBuild = - if ($null -eq $WMIos.Version) - { (Get-CimInstance Win32_OperatingSystem).Version } - else - { $WMIos.Version } - # Operating System Architecture - [String] $Device.Architecture = - if ($Device.OSBuild -le "6.0") - { - if ($Device.OSName -like "*64*") - { "64-bit" } else { "32-bit" } - } else { $WMIos.OSArchitecture } - # Program Files Location - [String] $Device.PF32 = - if ($Device.Architecture -eq "64-bit") - { ($env:SystemDrive + "\Program Files (x86)") } - else - { ($env:SystemDrive + "\Program Files") } - [String] $Device.PF = ($env:SystemDrive + "\Program Files") - # Server Core Installation - $CoreSKUs = @(12..14), 29, @(39..41), @(43..46), 53, 63, 64 - [Boolean] $Device.ServerCore = - if ($CoreSKUs -contains $WMIos.OperatingSystemSKU) - { $true } else { $false } -} - -function GetNETVersion -{ ### Function Body - ############################### - ### Retrieve .NET Framework Version - $NETinfo = - Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse | - Get-ItemProperty -Name Version, Release -ErrorAction SilentlyContinue | - Where-Object { $_.PSChildName -eq "Full" } | - Select-Object Version, Release, - @{ - Name = "Product" - Expression = { - $NET = $_ - switch ($NET) - { - { $_.Version -eq "4.0.30319" } - { "4.0"; break } - { $_.Release -eq 378389 } - { "4.5"; break } - { @(378675, 378758) -contains $_.Release } - { "4.5.1"; break } - { $_.Release -eq 379893 } - { "4.5.2"; break } - { @(393295, 393297) -contains $_.Release } - { "4.6"; break } - { @(394254, 394271) -contains $_.Release } - { "4.6.1"; break } - { @(394802, 394806) -contains $_.Release } - { "4.6.2"; break } - { @(460798, 460805) -contains $_.Release } - { "4.7"; break } - { @(461308, 461310) -contains $_.Release } - { "4.7.1"; break } - { @(461808, 461814) -contains $_.Release } - { "4.7.2"; break } - { @(528040, 528049) -contains $_.Release } - { "4.8"; break } - { $_.Release -gt 528049 } - { "4.8"; $DisplayProduct = "4.8 or Newer"; break } - Default - { - $NETVer = $NET.Version.Split(".") - $Product = @($NETVer[0], $NETVer[1]) -join '.' - $Product - $DisplayProduct = ($Product + " (Unverified)") - break - } - } - } - } - ### Summarize Version Info - if ($null -ne $NETinfo) - { - $Device.NETVersion = [Version] (ValidateVersion $($NETinfo.Version)) - $Device.NETProduct = [Version] (ValidateVersion $($NETinfo.Product)) - $Device.NETDisplayProduct = - if ($null -ne $DisplayProduct) - { $DisplayProduct } else { $Device.NETProduct } - } -} - -function SelfElevate -{ ### Function Body - ############################### - $CU_ID = [System.Security.Principal.WindowsIdentity]::GetCurrent() - $CU_Principal = New-Object System.Security.Principal.WindowsPrincipal($CU_ID) - $Admin_Role = [System.Security.Principal.WindowsBuiltInRole]::Administrator - if (-not $CU_Principal.IsInRole($Admin_Role)) - { - $Proc = New-Object System.Diagnostics.ProcessStartInfo "PowerShell" - $Proc.Arguments = ("-ExecutionPolicy Bypass -NoLogo -NoProfile -WindowStyle Hidden -File `"" + $Script.Invocation + "`"") - $Script.Parameters.Keys | - ForEach-Object { - $i = $_ - $Proc.Arguments += - if ($Script.Parameters.$i -like "* *") - { (" -" + $i + " `"" + $Script.Parameters.$i) } - else - { (" -" + $i + " " + $Script.Parameters.$i) } - } - if ($Device.OSBuild -gt "6.0") { $Proc.Verb = "runas" } - [System.Diagnostics.Process]::Start($Proc) >$null - exit - } -} - -### VALIDATION FUNCTIONS -############################### - -function ValidateVersion -{ ### Parameters - ############################### - param ($Version, $Digits) - ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Parameter Validation - ############################### - # Version - Must be a Valid Version String - $Version = [String] $Version - if ($Version -notmatch $SC.Validation.VersionNumber.Accepted) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "Version" - $Script.Results.GivenParameter = ($Version + " - Must be a Valid Version String") - Quit 100 - } - # Places - Must be an Integer between 2 and 4 - if ($null -eq $Digits) - { $Digits = 4 } - else - { - if ($Digits -notmatch $SC.Validation.VersionNumberDigits) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "Digits" - $Script.Results.GivenParameter = ([String]($Digits) + " - Must be an Integer (2-4)") - Quit 100 - } - } - ### Function Body - ############################### - $NewVersion = $Version - $VerCount = $Version.Split('.').Count - while ($VerCount -lt $Digits) - { - $NewVersion += ".0" - $VerCount = ($NewVersion).Split('.').Count - } - while ($VerCount -gt $Digits) - { - if ($NewVersion -eq $NewVersion.TrimEnd('0').TrimEnd('.')) - { break } else { $NewVersion = $NewVersion.TrimEnd('0').TrimEnd('.') } - $VerCount = $NewVersion.Split('.').Count - } - return $NewVersion -} - -function ValidateItem -{ ### Parameters - ############################### - param ($Path, [Switch] $Folder, [Switch] $NoNewItem, [Switch] $RemoveItem) - ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Parameter Validation - ############################### - # Path - Must be a Local Absolute Path - $Path | - ForEach-Object { - if ($_ -notmatch $SC.Validation.LocalFolderPath) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "Path" - $Script.Results.GivenParameter = ($Version + " (Must be a Local Absolute Path)") - Quit 100 - } - } - ### Function Body - ############################### - $RequiredType = - if ($Folder -eq $true) - { "Container" } else { "Leaf" } - $ImposterType = - if ($Folder -eq $true) - { "Leaf" } else { "Container" } - $NewItemType = - if ($Folder -eq $true) - { "Directory" } else { "File" } - $Path | - ForEach-Object { - $p = $_ - # Check for and Remove Imposters - if ((Test-Path $p -PathType $ImposterType) -eq $true) - { Remove-Item $p -Recurse -Force 2>$null } - if ($NoNewItem -eq $false) - { # Create an Empty Item if Required - if ((Test-Path $p) -eq $false) - { New-Item $p -ItemType $NewItemType -Force >$null 2>$null } - } - $ValidateResult += - if ($RemoveItem -eq $true) - { - Remove-Item $p -Recurse -Force 2>$null - @((Test-Path $p) -eq $false) - } - else - { @((Test-Path $p -PathType $RequiredType) -eq $true) } - } - return $ValidateResult -} - -function ValidatePartnerConfig -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - $Script.Execution.ScriptAction = "Validating Partner Configuration" - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Branding Values - $Partner.Config.Branding.GetEnumerator() | - Where-Object { $_.Name -ne '#comment' } | - ForEach-Object { $Config.$($_.Name) = $_.'#text' } - ### Script Behavior Values - $Partner.Config.ScriptBehavior.GetEnumerator() | - Where-Object { $_.Name -ne '#comment' } | - ForEach-Object { $Config.$($_.Name) = $_.'#text' } - ### Server Values - $Partner.Config.Server.GetEnumerator() | - Where-Object { $_.Name -ne '#comment' } | - ForEach-Object { $Config.$($_.Name) = $_.'#text' } - ### Service Behavior Values - $Partner.Config.ServiceBehavior.GetEnumerator() | - Where-Object { $_.Name -ne '#comment' } | - ForEach-Object { $Config.$("Service" + $_.Name) = $_.'#text' } - ### Deployment Values - $Config.LocalFolder = $Partner.Config.Deployment.LocalFolder - $Config.NetworkFolder = $Partner.Config.Deployment.NetworkFolder - # Installer Values - if ($Device.OSBuild -gt "6.0") - { # Use Typical (Latest) Agent - $InstallInfo = $Partner.Config.Deployment.Typical - } - else - { # Use Legacy Agent (Retain Support for Windows XP/Server 2003) - $InstallInfo = $Partner.Config.Deployment.Legacy - } - $Config.InstallFolder = $InstallInfo.InstallFolder - $Config.AgentFile = $InstallInfo.SOAgentFileName - $Config.AgentVersion = $InstallInfo.SOAgentVersion - $Config.AgentFileVersion = $InstallInfo.SOAgentFileVersion - $Config.NETFile = $InstallInfo.NETFileName - $Config.NETVersion = $InstallInfo.NETVersion - $Config.NETFileVersion = $InstallInfo.NETFileVersion - ### Function Body - ############################### - ### Validate Required Items from Partner Configuration - $ConfigUpdate = @{} - $Config.Keys | - ForEach-Object { - $i = $_ - $Invalid = - switch ($i) - { # Branding Values - "ErrorContactInfo" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # No Validation for Branding - $false - break - } - # Script Behavior Values - "BootTimeWaitPeriod" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Number from 0 to 60 - if ( - ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto2Digit) -or - ( - ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto2Digit) -and - ((([Int] $ConfigUpdate.$i) -lt 0) -or (([Int] $ConfigUpdate.$i) -gt 60)) - ) - ) - { $true } - else - { - $false - # Convert Minutes to Seconds - $ConfigUpdate.$i = [Int] $ConfigUpdate.$i * 60 - } - break - } - "InstallTimeoutPeriod" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Number from 1 to 60 - if ( - ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto2Digit) -or - ( - ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto2Digit) -and - ((([Int] $ConfigUpdate.$i) -lt 1) -or (([Int] $ConfigUpdate.$i) -gt 60)) - ) - ) - { $true } - else - { - $false - # Convert to Integer - $ConfigUpdate.$i = [Int] $ConfigUpdate.$i - } - break - } - # Server Values - "NCServerAddress" - { # Remove Outlying Blanks/Slashes if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim('/ ') } - # Remove Protocol Header if Present - if ($ConfigUpdate.$i -match $NC.Validation.ServerAddress.Accepted) - { $ConfigUpdate.$i = ($ConfigUpdate.$i).Split('/')[2] } - # Must be a Valid Server Address - if ($ConfigUpdate.$i -notmatch $NC.Validation.ServerAddress.Valid) - { $true } else { $false } - break - } - "PingCount" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Number from 1 to 100 - if ( - ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto3Digit) -or - ( - ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto3Digit) -and - ((([Int] $ConfigUpdate.$i) -lt 1) -or (([Int] $ConfigUpdate.$i) -gt 100)) - ) - ) - { $true } - else - { - $false - # Convert to Integer - $ConfigUpdate.$i = [Int] $ConfigUpdate.$i - } - break - } - "PingTolerance" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Number from 0 to 100 - if ( - ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto3Digit) -or - ( - ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto3Digit) -and - ((([Int] $ConfigUpdate.$i) -lt 0) -or (([Int] $ConfigUpdate.$i) -gt 100)) - ) - ) - { $true } - else - { - $false - # Convert to Integer - $ConfigUpdate.$i = [Int] $ConfigUpdate.$i - } - break - } - "ProxyString" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # No Validation for Proxy String - $false - break - } - # Service Behavior Values - { $_ -match '^ServiceAction.$' } - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be one of these Values and No Previous Service Actions can be Empty - if ( - ( - ($null -ne $ConfigUpdate.$i) -and - ($ConfigUpdate.$i -ne 'RESTART') -and - ($ConfigUpdate.$i -ne 'RUN') -and - ($ConfigUpdate.$i -ne 'REBOOT') - ) -or - ( - ($i -eq 'ActionB') -and - ($null -ne $ConfigUpdate.$i) -and - ($null -eq $ConfigUpdate.$($i -creplace ('B$', 'A'))) - ) -or - ( - ($i -eq 'ActionC') -and - ($null -ne $ConfigUpdate.$i) -and - ( - ($null -eq $ConfigUpdate.$($i -creplace ('C$', 'A'))) -or - ($null -eq $ConfigUpdate.$($i -creplace ('C$', 'B'))) - ) - ) - ) - { $true } else { $false } - break - } - "ServiceCommand" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # No Validation for Command - $false - break - } - { $_ -match '^ServiceDelay.$' } - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Assume No Delay if there is No Corresponding Action - if ($null -eq $ConfigUpdate.$($i -creplace (('Delay' + $i[-1]), ('Action' + $i[-1])))) - { $ConfigUpdate.$i = $null } - # Must be a Number from 0 to 3600 - if ( - ($null -ne $ConfigUpdate.$i) -and - ( - ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto4Digit) -or - ( - ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto4Digit) -and - ((([Int] $ConfigUpdate.$i) -lt 0) -or (([Int] $ConfigUpdate.$i) -gt 3600)) - ) - ) - ) - { $true } - else - { - $false - # Convert to Integer (Seconds to Milliseconds) - $ConfigUpdate.$i = - if ($null -ne $ConfigUpdate.$i) - { ([Int] $ConfigUpdate.$i) * 1000 } else { [Int] 0 } - } - break - } - "ServiceReset" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Number from 0 to 44640 - if ( - ($null -ne $ConfigUpdate.$i) -and - ( - ($ConfigUpdate.$i -notmatch $SC.Validation.WholeNumberUpto5Digit) -or - ( - ($ConfigUpdate.$i -match $SC.Validation.WholeNumberUpto5Digit) -and - ((([Int] $ConfigUpdate.$i) -lt 0) -or (([Int] $ConfigUpdate.$i) -gt 44640)) - ) - ) - ) - { $true } - else - { - $false - # Convert to Integer (Minutes to Seconds) - $ConfigUpdate.$i = - if ($null -ne $ConfigUpdate.$i) - { ([Int] $ConfigUpdate.$i) * 60 } else { [Int] 0 } - } - break - } - "ServiceStartup" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Accept Partial String Values - if ("Automatic" -like ($ConfigUpdate.$i + "*")) - { - $j = $i.Replace('Startup', '') - $ConfigUpdate.$i = "Automatic" - $ConfigUpdate.$($j + 'QueryString') = "Auto" - $ConfigUpdate.$($j + 'RepairString') = "Auto" - $ConfigUpdate.$($j + 'RequireDelay') = $false - } - if ("Delay" -like ($ConfigUpdate.$i + "*")) - { - $j = $i.Replace('Startup', '') - $ConfigUpdate.$i = "Delay" - $ConfigUpdate.$($j + 'QueryString') = "Auto" - $ConfigUpdate.$($j + 'RepairString') = "Delayed-Auto" - $ConfigUpdate.$($j + 'RequireDelay') = $true - } - # Must be one of these Values - if (@("Automatic", "Delay") -notcontains $ConfigUpdate.$i) - { $true } else { $false } - break - } - # Deployment Values - "AgentFile" - { # Remove Outlying Blanks/Periods if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim('. ') } - # Must be a Valid Executable File Name - if ($ConfigUpdate.$i -notmatch $SC.Validation.FileNameEXE) - { $true } else { $false } - break - } - "AgentFileVersion" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Version Number with up to 4 Digits - if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Accepted) - { $true } - else - { - $false - # Fill Empty Trailing Values with Zeroes - if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Valid) - { $ConfigUpdate.$i = ValidateVersion $($ConfigUpdate.$i) } - # Convert to Version - $ConfigUpdate.$i = [Version] $ConfigUpdate.$i - } - break - } - "AgentVersion" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Version Number with up to 4 Digits - if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Accepted) - { $true } - else - { - $false - # Fill Empty Trailing Values with Zeroes - if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Valid) - { $ConfigUpdate.$i = ValidateVersion $($ConfigUpdate.$i) } - # Convert to Version - $ConfigUpdate.$i = [Version] $ConfigUpdate.$i - } - break - } - "InstallFolder" - { # Remove Outlying Blanks/Periods if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim('. ') } - # Must be a Valid Folder Name - if ($ConfigUpdate.$i -notmatch $SC.Validation.ItemName) - { $true } else { $false } - break - } - "LocalFolder" - { # Remove Trailing Slash if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim('\ ') } - # Must be a Local Absolute Path - if ($ConfigUpdate.$i -notmatch $SC.Validation.LocalFolderPath) - { $true } else { $false } - break - } - "NETFile" - { # Remove Outlying Blanks/Periods if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim('. ') } - # Must be a Valid Executable File Name - if ($ConfigUpdate.$i -notmatch $SC.Validation.FileNameEXE) - { $true } else { $false } - break - } - "NETFileVersion" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Version Number with up to 4 Digits - if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Accepted) - { $true } - else - { - $false - # Fill Empty Trailing Values with Zeroes - if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Valid) - { $ConfigUpdate.$i = ValidateVersion $($ConfigUpdate.$i) } - # Convert to Version - $ConfigUpdate.$i = [Version] $ConfigUpdate.$i - } - break - } - "NETVersion" - { # Remove Outlying Blanks if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim() } - # Must be a Version Number with up to 4 Digits - if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Accepted) - { $true } - else - { - $false - # Fill Empty Trailing Values with Zeroes - if ($ConfigUpdate.$i -notmatch $SC.Validation.VersionNumber.Valid) - { $ConfigUpdate.$i = ValidateVersion $($ConfigUpdate.$i) } - # Convert to Version - $ConfigUpdate.$i = [Version] $ConfigUpdate.$i - } - break - } - "NetworkFolder" - { # Remove Outlying Blanks/Periods if Present - $ConfigUpdate.$i = - if (@($null, '') -notcontains $Config.$i) - { ($Config.$i).Trim('. ') } - # Must be a Valid Folder Name - if ($ConfigUpdate.$i -notmatch $SC.Validation.ItemName) - { $true } else { $false } - break - } - } - if ($Invalid -eq $true) - { $InvalidConfig += @($i) } - } - # Update Config Table - $ConfigUpdate.Keys | - ForEach-Object { $Config.$_ = $ConfigUpdate.$_ } - # Report on any Invalid Configuration Items - if ($null -ne $InvalidConfig) - { - $Out = - "One or more items in the Partner Configuration was invalid.`n", - "Please verify the following values:" - $InvalidConfig | - Sort-Object | - ForEach-Object { $Out += @($_) } - Log E 2 $Out -Exit - } - else - { - $Out = @("All Required values in the Partner Configuration were successfully validated.") - Log I 0 $Out - } - ### Populate Configuration History Location from Partner Configuration - if ((ValidateItem $Config.LocalFolder -Folder) -eq $false) - { # ERROR - Unable to Validate Configuration History Folder - CatchError 104 "The Script was unable to validate the Configuration History Folder." -Exit - } - $Agent.Path.History = @($Config.LocalFolder, $SC.Names.HistoryFile) -join '\' -} - -function ValidateExecution -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - $Script.Execution.ScriptAction = "Determining Execution Mode" - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Function Body - ############################### - ### Build Installation Source Paths - $Install.Sources = @{ - "Demand" = @{ "Path" = ($LauncherPath + $Config.InstallFolder) } - "Network" = @{ - "Path" = @( - "\", $Device.FQDN, "NETLOGON", - $Config.NetworkFolder, $Config.InstallFolder - ) -join '\' - } - } - ### Determine Execution Mode - $Script.Execution.ScriptMode = - switch ($Install.Sources.Demand.Path) - { - $Install.Sources.Network.Path - { $SC.ExecutionMode.B; break } - Default - { $SC.ExecutionMode.A; break } - } - ### Log Execution Mode - WriteKey $Script.Results.ScriptKey $Script.Execution - # Wait if Device has Recently Booted - if ( - ($null -ne $Device.LastBootTime) -and - ($Device.LastBootTime -ge (Get-Date).AddSeconds(-($Config.BootTimeWaitPeriod))) - ) - { # Update Execution Info - $Script.Execution.ScriptAction = "Waiting Before Diagnosis" - WriteKey $Script.Results.ScriptKey $Script.Execution - # Wait for Required Duration - [Int] $WaitTime = - $Config.BootTimeWaitPeriod - ( - ((Get-Date) - ([DateTime] $Device.LastBootTime)) | - Select-Object -ExpandProperty TotalSeconds - ) - $Out = - ("Windows has booted within the " + $Config.BootTimeWaitPeriod + "-second Wait Period specified in the Partner Config.`n"), - ("Waiting the remaining " + $WaitTime + " seconds before Diagnosis...") - Log I 0 $Out - Start-Sleep -Seconds $WaitTime - } -} - -### DIAGNOSIS FUNCTIONS -############################### - -function ReadXML -{ ### Parameters - ################################ - param ($XMLContent, $XPath) - ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Parameter Validation - ################################ - ### XMLContent - Must be Full XML Text or an Absolute Path to an XML File - # Validate Full XML Text - if ($XMLContent -isnot [Xml]) - { # Validate Absolute XML Path - if ($XMLContent -match $SC.Validation.LocalFilePathXML) - { # Verify File Exists - if ((Test-Path $XMLContent -PathType Leaf 2>$null) -eq $true) - { [Xml] $XMLContent = Get-Content $XMLContent } - else - { # ERROR - File Does Not Exist - $Script.Results.Parameter = "XMLContent" - $Script.Results.GivenParameter = ("[" + $XMLContent + "] - The XML File does not Exist") - Quit 104 - } - } - else - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "XMLContent" - $Script.Results.GivenParameter = "Must be Valid XML File Content OR Absolute Path to an XML File" - Quit 100 - } - } - # XPath - Must be an XML Path to an Existing Element (Document Element if omitted) - ## Start with /, remove trailing / - if ($null -eq $XPath) - { $XPath = @("", $XMLContent.DocumentElement.Name, "*") -join '/' } - else - { - if ($XPath -notmatch $SC.Validation.XMLElementPath) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "XPath" - $Script.Results.GivenParameter = ("[" + $XPath + "] - Must be a Valid XML Path String (e.g. /MyConfig/Settings/*)") - Quit 100 - } - } - ### Function Body - ################################ - $Hash = @{} - # Collect the Properties in the Current Element - $XMLContent.SelectNodes($XPath) | - Where-Object { $_.IsEmpty -eq $false } | - ForEach-Object { - $Node = $_ - switch ($Node.ChildNodes | Select-Object -ExpandProperty NodeType) - { # Store Values from Current Element - "Text" - { $Hash.$($Node.Name) = $Node.ChildNodes | Select-Object -ExpandProperty Value; break } - # Iterate through Elements for more Values - "Element" - { $Hash.$($Node.Name) = ReadXML $XMLContent $(@(($XPath -split '/\*')[0], $Node.Name, '*') -join '/'); break } - } - } - return $Hash -} - -function WriteXML -{ ### Parameters - ############################### - param ($XMLPath, $Root, $Values) - ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Parameter Validation - ############################### - # XMLPath - Must be an Absolute Path - if ($XMLPath -notmatch $SC.Validation.LocalFilePathXML) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "XMLPath" - $Script.Results.GivenParameter = ("[" + $XMLPath + "] - Must be an Absolute File Path with XML Extension") - Quit 100 - } - # Root - Must be a Valid XML Element Name - if ($Root -notmatch $SC.Validation.XMLElementName) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "Root" - $Script.Results.GivenParameter = "Must be a Valid XML Element Name" - Quit 100 - } - # Values - Must be a Hashtable with a Single Root Key - if ($Values -isnot [Hashtable]) - { # ERROR - Invalid Parameter - $Script.Results.Parameter = "Values" - $Script.Results.GivenParameter = "Must be a Hashtable with a Single Root Key" - Quit 100 - } - ### Function Body - ############################### - ### Remove XML Document if Required - if ((Test-Path $XMLPath) -eq $true) - { Remove-Item $XMLPath -Force 2>$null } - ### Create a New XML Document with Provided Values - [Xml] $XMLDoc = New-Object System.Xml.XmlDocument - # Write the XML Declaration - $Declaration = $XMLDoc.CreateXmlDeclaration("1.0", "UTF-8", $null) - $XMLDoc.AppendChild($Declaration) >$null - # Write the Root Element - $RootElement = $XMLDoc.CreateElement($Root) - $XMLDoc.AppendChild($RootElement) >$null - # Write Elements - $Values.Keys | - Sort-Object | - ForEach-Object { - $Element = $XMLDoc.CreateElement($_) - $Text = $XMLDoc.CreateTextNode($Values.$_) - $Element.AppendChild($Text) >$null - $RootElement.AppendChild($Element) >$null - } - $XMLDoc.Save($XMLPath) -} - -function QueryServices -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - ### Get Agent Service / Process Info - $($Agent.Services.Data.Keys) | - ForEach-Object { - $s = $_ - # Get Service Status - $Agent.Services.Data.$s = Get-WmiObject Win32_Service -Filter "Name='$s'" - if ($null -ne $Agent.Services.Data.$s) - { # Validate Related Process Info - $P0 = ($Agent.Services.Data.$s.PathName).Split()[0] - $P1 = - switch ($P0) - { - { ($_ -match '^"') -and ($_ -match '"$') } - { $P0.Trim('"'); break } - { $_ -match '^"' } - { ($Agent.Services.Data.$s.PathName).Split('"')[1]; break } - { $_ -match '\.\S{3,}$' } - { $P0; break } - Default - { $Agent.Services.Data.$s.PathName; break } - } - $p = $P1.Split('\')[-1] - # Report Process Status - $Agent.Processes.$s = @{ - "FilePath" = $P1 - "Name" = $p -replace '\.\S{3,}$', '' - "ID" = - if ($Agent.Services.Data.$s.ProcessID -eq 0) - { $null } else { $Agent.Services.Data.$s.ProcessID } - } - ### Get Service Failure Behavior - $FailureRaw = & SC.EXE QFAILURE $s 5000 - # Get Service Reset Period - $FailureReset = ( - ( - $FailureRaw | - Where-Object { $_ -like "*RESET_PERIOD*" } - ) -split (' : ') - )[-1] - # Get Service Failure Actions - $ActionIndex = - [Array]::IndexOf( - $FailureRaw, - ($FailureRaw | Where-Object { $_ -like "*FAILURE_ACTIONS*" }) - ) - if ($ActionIndex -ge 0) - { - $FailureRaw[$ActionIndex..($ActionIndex + 2)] | - Where-Object { $_ -ne '' } | - ForEach-Object { - $F1 = ($_ -split (' : '))[-1] - $FailureActions += @(($F1 -split (' -- '))[0].Trim()) - $FailureDelays += @(((($F1 -split (' -- '))[-1] -split (' = '))[-1]).Split(' ')[0]) - } - } - # Get Service Failure Command (if present) - $FailureCommand = ( - ( - $FailureRaw | - Where-Object { $_ -like "*COMMAND_LINE*" } - ) -split (' : ') - )[-1] - if ($FailureCommand -eq '') - { $FailureCommand = $null } - # Report Failure Behavior Status - $Agent.Services.Failure.$s = @{ - "Actions" = @{} - "Command" = $FailureCommand - "Delays" = @{} - "Reset" = $FailureReset - } - for ($i = 0; $i -lt 3; $i++) - { - $Agent.Services.Failure.$s.Actions.$(AlphaValue $i) = - if ($null -ne $FailureActions) - { $FailureActions[$i] } else { $null } - } - for ($i = 0; $i -lt 3; $i++) - { - $Agent.Services.Failure.$s.Delays.$(AlphaValue $i) = - if ($null -ne $FailureDelays) - { $FailureDelays[$i] } else { $null } - } - } - else - { # Write Dummy Values for Process and Service Failure Behavior - $Agent.Processes.$s = $null - $Agent.Services.Failure.$s = $null - } - } - ### Report Current Status - # Agent Exectuables Exist - $Agent.Health.ProcessesExist = - if ( - ( - $Agent.Processes.GetEnumerator() | - Where-Object { $null -ne $_.Value.FilePath } | - ForEach-Object { (Test-Path $_.Value.FilePath -PathType Leaf) -eq $true } - ).Count -eq $Agent.Services.Data.Count - ) - { $true } else { $false } - # Agent Processes Running - $Agent.Health.ProcessesRunning = - if ( - ($Agent.Health.ProcessesExist -eq $true) -and - ( - ( - $Agent.Processes.GetEnumerator() | - ForEach-Object { $null -ne $_.Value.ID } - ) -notcontains $false - ) - ) - { $true } else { $false } - # Agent Services Exist - $Agent.Health.ServicesExist = - if ($Agent.Services.Data.Values -notcontains $null) - { $true } else { $false } - # Agent Services Failure Behavior Configured - $Agent.Health.ServicesBehaviorCorrect = - if ( - ( - $Agent.Services.Failure.GetEnumerator() | - ForEach-Object { - switch ($_) - { - { $null -eq $_.Value } - { $false; break } - { - ( - ( - ( - $_.Value.Actions.GetEnumerator() | - ForEach-Object { - if ($null -ne $_.Value) - { $_.Value.Split()[0] -eq $Config.$("ServiceAction" + $_.Name) } - else { $false } - } - ) -notcontains $false - ) -and - ( - ( - $_.Value.Delays.GetEnumerator() | - ForEach-Object { $_.Value -eq $Config.$("ServiceDelay" + $_.Name) } - ) -notcontains $false - ) -and - ($_.Value.Reset -eq $Config.ServiceReset) -and - ($_.Value.Command -eq $Config.ServiceCommand) - ) - } - { $true; break } - Default - { $false; break } - } - } - ) -notcontains $false - ) - { $true } else { $false } - # Agent Services Running - $Agent.Health.ServicesRunning = - if ( - ($Agent.Health.ServicesExist -eq $true) -and - ( - ( - $Agent.Services.Data.GetEnumerator() | - ForEach-Object { $_.Value.State -eq "Running" } - ) -notcontains $false - ) - ) - { $true } else { $false } - # Agent Services Startup Configured - $Agent.Health.ServicesStartupCorrect = - if ( - ( - $Agent.Services.Data.GetEnumerator() | - ForEach-Object { - switch ($_) - { - { $null -eq $_.Value } - { $false; break } - { - ($_.Value.StartMode -eq $Config.ServiceQueryString) -and - ( - ($_.Value.DelayedAutoStart -eq $Config.ServiceRequireDelay) -or - @( - if ( - ( - Get-ItemProperty (@($SC.Paths.ServiceKey, $_.Value.Name) -join '\') 2>$null | - Select-Object -ExpandProperty DelayedAutoStart 2>$null - ) -eq 1 - ) - { $Config.ServiceRequireDelay -eq $true } else { $Config.ServiceRequireDelay -eq $false } - ) - ) - } - { $true; break } - Default - { $false; break } - } - } - ) -notcontains $false - ) - { $true } else { $false } -} - -function TestNCServer -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - # Ping N-Central Server and Google DNS - for ($i = 1; $i -le $Config.PingCount; $i++) - { - $PingNCTest += @(Test-Connection $Config.NCServerAddress -Count 1 -Quiet) - Start-Sleep -Milliseconds 300 - $PingGoogleTest += @(Test-Connection '8.8.8.8' -Count 1 -Quiet) - Start-Sleep -Milliseconds 300 - } - # Check if Results Pass the Partner Threshold - $NCSuccessRate = ($PingNCTest -like $true).Count / $PingNCTest.Count - $NCResult = - if ((($PingNCTest -like $true).Count / $PingNCTest.Count) -ge ($Config.PingTolerance / 100)) - { $true } else { $false } - $GoogleSuccessRate = ($PingGoogleTest -like $true).Count / $PingGoogleTest.Count - $GoogleResult = - if ($GoogleSuccessRate -ge ($Config.PingTolerance / 100)) - { $true } else { $false } - # Evaluate and Log Connectivity Check Result - switch ($NCResult) - { - $false - { - $Out = @( - switch ($GoogleResult) - { - $false - { "Device appears not to have Internet connectivity at present.`n"; break } - $true - { ("Device appears to have Internet connectivity, but is unable to reliably connect to the " + $NC.Products.NCServer.Name + ".`n"); break } - }, - "The Script will assess and perform Offline Repairs where possible until connectivity is restored." - ) - Log W 0 $Out - } - $true - { - $Out = @( - switch ($GoogleResult) - { - $false - { - switch ($NCSuccessRate) - { - { $_ -lt 1 } - { # Warn on Dropped Packets - $Flag = "W" - ("Device appears to have connectivity to the " + $NC.Products.NCServer.Name + ", but is dropping some packets.") - break - } - 1 - { - $Flag = "I" - ("Device has reliable connectivity to the " + $NC.Products.NCServer.Name + ".") - break - } - }, - "However, the general Internet Connectivity Test failed. It's possible Google DNS Server is experiencing issues at present." - break - } - $true - { - switch ($NCSuccessRate) - { - { $_ -lt 1 } - { # Warn on Dropped Packets - $Flag = "W" - ("Device appears to have connectivity to the " + $NC.Products.NCServer.Name + ", but is dropping some packets.") - break - } - 1 - { - $Flag = "I" - ("Device has reliable connectivity to the " + $NC.Products.NCServer.Name + ".") - break - } - }, - "General Internet Connectivity is reliable." - break - } - } - ) - Log $Flag 0 $Out - break - } - } - # Check if Agent has Connectivity to Server in Partner Configuration - $Install.NCServerAccess = - if ($null -ne $Flag) - { $true } else { $false } -} - -function DiagnoseAgent -{ ### Parameters - ############################### - param ([Switch] $NoLog, [Switch] $NoServerCheck) - ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - switch ($Script.Sequence.Order[-1]) - { - $SC.SequenceNames.D - { - Log I 0 "Re-Checking Agent Health after Repair Action(s)..." - break - } - $SC.SequenceNames.E - { - if ($NoLog -eq $false) - { Log I 0 "Re-Checking Agent Health after Install Action..." } - break - } - Default - { - $Script.Execution.ScriptAction = "Diagnosing Existing Installation" - WriteKey $Script.Results.ScriptKey $Script.Execution - break - } - } - ### Function Body - ############################### - ### Initialize/Clear Diagnostics Tables - $Agent.Appliance = @{} - $Agent.Docs = @{ - $NC.Products.Agent.ApplianceConfig = @{} - $NC.Products.Agent.InstallLog = @{} - $SC.Names.HistoryFile = @{} - "Registry" = @{} - } - $Agent.Health = @{} - $Agent.History = @{} - ### Get Info About the Current Installation - # Review Appliance Configuration - if ((Test-Path $Agent.Path.ApplianceConfig -PathType Leaf) -eq $true) - { # Read Appliance Config XML - $Agent.Docs.$($NC.Products.Agent.ApplianceConfig) = ReadXML $Agent.Path.ApplianceConfig - # Read Server Config XML - $Agent.Docs.$($NC.Products.Agent.ServerConfig) = ReadXML $Agent.Path.ServerConfig - } - # Review Checker Log - if ((Test-Path $Agent.Path.Checker -PathType Leaf) -eq $true) - { # Retrieve Values - $NC.Products.Agent.InstallLogFields | - ForEach-Object { - $t_Found = Select-String ($_ + "\s+:") $Agent.Path.Checker - $Agent.Docs.$($NC.Products.Agent.InstallLog).$_ = - if ($null -ne $t_Found) - { ($t_Found.Line -split (' : '))[1].Trim() } else { $null } - } - } - # Detect and Review Registry Uninstall Key - $Agent.Path.Registry = ( - Get-ChildItem $NC.Paths.$("UninstallKey" + $Device.Architecture.Split('-')[0]) | - ForEach-Object { Get-ItemProperty $_.PSPath } | - Where-Object { $_.DisplayName -eq $NC.Products.Agent.WindowsName } - ).PSPath - if ($null -ne $Agent.Path.Registry) - { - $Agent.Docs.Registry = - Get-ItemProperty $Agent.Path.Registry | - Get-Member -MemberType NoteProperty | - Select-Object -ExpandProperty Name | - ForEach-Object { @{ $_ = (Get-ItemProperty $Agent.Path.Registry).$_ } } - } - ### Get Info About Last Known Installation (in case Agent is Missing) - if ((Test-Path $Agent.Path.History -PathType Leaf) -eq $true) - { # Read Agent Configuration History XML - $Agent.Docs.$($SC.Names.HistoryFile) = ReadXML $Agent.Path.History - } - ### Validate All Discovered Info - $($Agent.Docs.Keys | Sort-Object) | - ForEach-Object { - $d = $_ - # Set the Appropriate Information Key for each Document - $($Agent.Docs.$d.Keys) | - ForEach-Object { - $i = $_ - # Set the Appropriate Table Keys, Value Names and Match Criteria - $AppInfo = - switch ($i) - { # Common Info - "Version" - { - switch ($d) - { - $SC.Names.HistoryFile - { @($i, $SC.Validation.VersionNumber.Valid); break } - Default - { $null; break } - } - break - } - # Appliance Unique Info - "ApplianceID" - { @("ID", $NC.Validation.ApplianceID); break } - "ApplianceVersion" - { @("Version", $SC.Validation.VersionNumber.Valid); break } - "CustomerID" - { @("SiteID", $NC.Validation.CustomerID); break } - # Checker Unique Info - "Activation Key" - { @("ActivationKey", $NC.Validation.ActivationKey.Encoded); break } - "Appliance ID" - { @("ID", $NC.Validation.ApplianceID); break } - "Customer ID" - { @("SiteID", $NC.Validation.CustomerID); break } - "Install Time" - { @("LastInstall", $null); break } - "Package Version" - { @("WindowsVersion", $SC.Validation.VersionNumber.Valid); break } - # History Unique Info - "ActivationKey" - { @($i, $NC.Validation.ActivationKey.Encoded); break } - "HistoryUpdated" - { @($i, $null); break } - "ID" - { @($i, $NC.Validation.ApplianceID); break } - "LastInstall" - { @($i, $null); break } - "ScriptSiteID" - { @($i, $NC.Validation.CustomerID); break } - "SiteID" - { @($i, $NC.Validation.CustomerID); break } - "WindowsVersion" - { @($i, $SC.Validation.VersionNumber.Valid); break } - # Registry Unique Info - "DisplayVersion" - { @($i, $SC.Validation.VersionNumber.Valid); break } - "InstallDate" - { @($i, $null); break } - "InstallLocation" - { @($i, $SC.Validation.LocalFolderPath); break } - "UninstallString" - { @($i, $null); break } - # Server Unique Info - "ServerIP" - { @("AssignedServer", $null); break } - Default - { $null; break } - } - # Apply Additional Formatting to Select Data - if ($null -ne $AppInfo) - { - $FormattedInfo = - if ($null -ne $Agent.Docs.$d.$i) - { - switch ($i) - { # Date Values - "InstallDate" - { - Get-Date ( - @( - -join $Agent.Docs.Registry.InstallDate[0..3], - -join $Agent.Docs.Registry.InstallDate[4..5], - -join $Agent.Docs.Registry.InstallDate[6..7] - ) -join '-' - ) -UFormat $SC.DateFormat.Short - } - { @("HistoryUpdated", "Install Time", "LastInstall") -contains $_ } - { - try - { Get-Date $Agent.Docs.$d.$i -UFormat $SC.DateFormat.Full } - catch - { $null } - break - } - # InstallLocation Value - "InstallLocation" - { ($Agent.Docs.$d.$i).Trim('\ '); break } - # Version Values - { - @( - "ApplianceVersion", "DisplayVersion", "Package Version", - "Version", "WindowsVersion" - ) -contains $_ - } - { - ValidateVersion $($Agent.Docs.$d.$i) - break - } - Default - { $Agent.Docs.$d.$i; break } - } - } - else { $null } - # Validate the Info - $ValidatedInfo = - if (($null -ne $FormattedInfo) -and ($FormattedInfo -match $AppInfo[1])) - { $FormattedInfo } else { $null } - if ($null -ne $ValidatedInfo) - { # Add Valid Info to Appliance/History Tables - if ($null -eq $Agent.$($SC.Validation.Docs.$d).$($AppInfo[0])) - { $Agent.$($SC.Validation.Docs.$d).$($AppInfo[0]) = $ValidatedInfo } - } - } - } - } - ### Build Activation Key/Appliance ID from Available Parts if Required - foreach ($t in @($($Agent.Appliance), $($Agent.History))) - { # Activation Key - if ($null -eq $t.ActivationKey) - { # Build Activation Key - if ($null -ne $t.ID) - { - $r = $($Agent) - $r.DecodedActivationKey = ( - "https://" + $Config.NCServerAddress + - ":443|" + $t.ID + "|1|0" - ) - # Add the Value - $t.ActivationKey = - [System.Convert]::ToBase64String( - [System.Text.Encoding]::UTF8.GetBytes($r.DecodedActivationKey) - ) - } - } - # Appliance ID - if ($null -eq $t.ApplianceID) - { # Extract Appliance ID - if ($null -ne $t.ActivationKey) - { - $r = $($Agent) - $r.DecodedActivationKey = - [System.Text.Encoding]::UTF8.GetString( - [System.Convert]::FromBase64String($t.ActivationKey) - ) - # Add the Value - $t.ID = $r.DecodedActivationKey.Split('|')[-3] - } - } - } - ### Update or Create Historical Configuration File if Required - UpdateHistory - ### Check for a Corrupt or Disabled Agent - QueryServices - ### Check for a Missing Agent - # Agent is Installed - $Agent.Health.Installed = - if ( - ($null -ne $Agent.Path.Registry) -and - ((ValidateItem $(@($Device.PF32, $NC.Paths.BinFolder, $NC.Products.Agent.Process) -join '\') -NoNewItem) -eq $true) - ) - { - if ((Test-Path $Agent.Path.Registry) -eq $true) - { $true } else { $false } - } else { $false } - # Log Discovered Agent Status - if (($Agent.Health.Installed -eq $true) -and ($NoLog -eq $false)) - { - $Out = @("Found:") - $Out += @( - switch ($Agent.Appliance) - { - { ($null -ne $_.Version) } - { - switch ($_.WindowsVersion) - { - $null - { ($NC.Products.Agent.Name + " Version - " + $Agent.Appliance.Version) } - Default - { ($NC.Products.Agent.Name + " Version - " + $Agent.Appliance.Version + " (Windows " + $Agent.Appliance.WindowsVersion + ")") } - } - } - { $null -eq $_.Version } - { - switch ($_.WindowsVersion) - { - $null - { ($NC.Products.Agent.Name + " Version - (Windows " + $Agent.Registry.DisplayVersion + ")") } - Default - { ($NC.Products.Agent.Name + " Version - (Windows " + $Agent.Appliance.WindowsVersion + ")") } - } - } - { $null -ne $_.LastInstall } - { ("Installed on " + (Get-Date $Agent.Appliance.LastInstall -UFormat $SC.DateFormat.FullMessageOnly)) } - { $null -eq $_.LastInstall } - { ("Installed on " + $InstallDate) } - } - ) - Log I 0 -Message $Out - } - ### Check if Appliance ID is Invalid - $Agent.Health.ApplianceIDValid = - if ($Agent.Docs.$($NC.Products.Agent.ApplianceConfig).ApplianceID -match $NC.Validation.ApplianceID) - { $true } else { $false } - ### Check if Installed Agent is Up to Date - $Agent.Health.VersionCorrect = - if ($Agent.Health.Installed) - { - if ( - ( - ($null -ne $Agent.Appliance.Version) -and - (([Version] $Agent.Appliance.Version) -ge ([Version] $Config.AgentVersion)) - ) -or - ( - ($null -ne $Agent.Appliance.WindowsVersion) -and - (([Version] $Agent.Appliance.WindowsVersion) -ge ([Version] $Config.AgentFileVersion)) - ) - ) - { $true } else { $false } - } else { $false } - ### Verify Connectivity to Partner Server - if ($NoServerCheck -eq $false) - { TestNCServer } - ### Check if Installed Agent Server Address matches Partner Configuration - $Agent.Health.AssignedToPartnerServer = - if ($Agent.Appliance.AssignedServer -eq $Config.NCServerAddress) - { $true } else { $false } - ### Summarize Agent Health - # Update Status - $Agent.Health.AgentStatus = - switch ($Agent.Health) - { - { $_.Values -notcontains $false } - { $SC.ApplianceStatus.A; break } - { $_.Installed -eq $false } - { $SC.ApplianceStatus.G; break } - { @($_.ProcessesExist, $_.ServicesExist) -contains $false } - { $SC.ApplianceStatus.F; break } - { $_.AssignedToPartnerServer -eq $false } - { - if ($Agent.Appliance.AssignedServer -eq $NC.Products.Agent.ServerDefaultValue) - { $SC.ApplianceStatus.C } else { $SC.ApplianceStatus.E } - break - } - { @($_.ProcessesRunning, $_.ServicesRunning) -contains $false } - { $SC.ApplianceStatus.D; break } - { $_.ApplianceIDValid -eq $false } - { $SC.ApplianceStatus.C; break } - { - @( - $_.VersionCorrect, - $_.ServicesBehaviorCorrect, - $_.ServicesStartupCorrect - ) -contains $false - } - { $SC.ApplianceStatus.B; break } - } - # Update Registry Values - $Script.Execution.AgentLastDiagnosed = Get-Date -UFormat $SC.DateFormat.Full - WriteKey $Script.Results.ScriptDiagnosisKey $Agent.Health - # Identify/Log Needed Repairs - if ($NoLog -eq $false) - { - $Out = @("Current Agent Status is " + $Agent.Health.AgentStatus + ":") - $Out += @( - switch ($Agent.Health.AgentStatus) - { - $SC.ApplianceStatus.A - { "No Action Required"; break } - { @($SC.ApplianceStatus.E, $SC.ApplianceStatus.F, $SC.ApplianceStatus.G) -contains $_ } - { "Installation Required"; break } - { @($SC.ApplianceStatus.C, $SC.ApplianceStatus.D) -contains $_ } - { "Repair Required"; break } - $SC.ApplianceStatus.B - { - switch ($Agent.Health) - { - { $_.VersionCorrect -eq $false } - { "Agent Update Required" } - { $_.ServicesBehaviorCorrect -eq $false } - { "Service Failure Behavior Adjustment Required" } - { $_.ServicesStartupCorrect -eq $false } - { "Service Startup Type Adjustment Required" } - } - break - } - } - ) - Log I 0 $Out - } - # Determine Sequence Behavior After Diagnosis - if (($Agent.Health.AgentStatus -eq $SC.ApplianceStatus.A) -and ($Script.Sequence.Order[-1] -eq $SC.SequenceNames.C)) - { # No Further Action Required After Initial Diagnosis - Log -EndSequence -Code 0 -Exit - } - # Proceed to Next Sequence or Return to Current Sequence -} - -### REPAIR FUNCTIONS -############################### - -function VerifyServices -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - $Script.Execution.ScriptAction = - switch ($Script.Sequence.Order[-1]) - { - $SC.SequenceNames.D - { "Verifying Service Repair"; break } - $SC.SequenceNames.E - { "Monitoring Agent Services Post-Install"; break } - } - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Function Body - ############################### - # Check Current Service Status - QueryServices - # Verify Processes Have Not Terminated or Restarted for 2 Minutes - do - { - $Agent.Services.Data.Keys | - ForEach-Object { - $s = $_ - $MatchID = $Agent.Processes.$s.ID - $ProcessFound = - if ($null -ne $MatchID) - { Get-Process -Id $MatchID 2>$null } - $ProcessMatch += @( - if ($null -ne $ProcessFound) - { - if ($ProcessFound.ProcessName -eq $Agent.Processes.$s.Name) - { $true } else { $false } - } else { $false } - ) - } - Start-Sleep 10 - } - while (($ProcessMatch -notcontains $false) -and ($ProcessMatch.Count -lt 12)) - # Complete/Fail Verification - if ($ProcessMatch.Count -ge 12) - { return $true } - else - { # Re-Check Current Service Status - QueryServices - # Re-check with the New PIDs (in case of Agent-issued Service Restart, Upgrade, etc.) - do - { - $Agent.Services.Data.Keys | - ForEach-Object { - $s = $_ - $MatchID = $Agent.Processes.$s.ID - $ProcessFound = - if ($null -ne $MatchID) - { Get-Process -Id $MatchID 2>$null } - $ProcessMatch += @( - if ($null -ne $ProcessFound) - { - if ($ProcessFound.ProcessName -eq $Agent.Processes.$s.Name) - { $true } else { $false } - } else { $false } - ) - Start-Sleep 10 - } - } - while (($ProcessMatch -notcontains $false) -and ($ProcessMatch.Count -lt 12)) - # Complete/Fail Verification - if ($ProcessMatch.Count -ge 12) - { return $true } else { return $false } - } -} - -function FixServices -{ ### Parameters - ############################### - param ([Switch] $Restart, [Switch] $Disable) - ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - $Agent.Services.Data.Keys | - ForEach-Object { - $s = $_ - if ($Disable -eq $true) - { ### Stop and Disable the Services Instead - & SC.EXE CONFIG "$s" START= "Disabled" >$null 2>$null - & TASKKILL.EXE /PID $Agent.Processes.$s.ID /F >$null 2>$null - Get-Service -Name $s 2>$null | Stop-Service 2>$null -WarningAction SilentlyContinue - } - else - { ### Start or Restart the Service - try - { - switch ($Agent.Services.Data.$s.State) - { - "Running" - { # Service Running - Attempt to Restart Only if Specified - if ($Restart -eq $true) - { - & TASKKILL.EXE /PID $Agent.Processes.$s.ID /F >$null 2>$null - if (@(0, 128) -contains $LASTEXITCODE) - { - Get-Service -Name $s | Stop-Service 2>$null -WarningAction SilentlyContinue - Get-Service -Name $s | Start-Service 2>&1 -ErrorAction Stop - } - else - { # Fail the Repair if the Process cannot be Killed - $RepairResult = - if ($RepairResult -ne $false) - { $false } else { $RepairResult } - } - } - break - } - "Stopped" - { # Service Stopped - Attempt to Start - Get-Service -Name $s | Start-Service 2>&1 -ErrorAction Stop - break - } - Default - { # Service Pending Action or Not Responding - Kill Process and Attempt to Start - & TASKKILL.EXE /PID $Agent.Processes.$s.ID /F >$null 2>$null - if (@(0, 128) -contains $LASTEXITCODE) - { - Get-Service -Name $s | Stop-Service 2>$null -WarningAction SilentlyContinue - Get-Service -Name $s | Start-Service 2>&1 -ErrorAction Stop - } - else - { # Fail the Repair if the Process cannot be Killed - $RepairResult = - if ($RepairResult -ne $false) - { $false } else { $RepairResult } - } - break - } - } - } - catch - { # Fail the Repair if the Service cannot Start - $RepairResult = - if ($RepairResult -ne $false) - { $false } else { $RepairResult } - } - } - } - if ($Disable -eq $false) - { # Re-Check Service/Process Status After Repair - $RepairResult = VerifyServices - # Complete the Repair unless it Otherwise Failed - $RepairResult = - if ($RepairResult -eq $true) - { $RepairResult } else { $false } - return $RepairResult - } -} - -function FixOrphanedAppliance -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - ### Replace the Appliance ID in the Appliance Configuration - [Xml] $XMLDoc = Get-Content $Agent.Path.ApplianceConfig - # Select the Most Recent Appliance ID to do the Replacement - $SelectedID = - switch ($Agent) - { - { $null -ne $_.Appliance.ID } - { $Agent.Appliance.ID; break } - { $null -ne $_.History.ID } - { $Agent.History.ID; break } - Default - { # Fail the Repair - No Valid Appliance ID Found - return $false - } - } - $XMLDoc.ApplianceConfig.ApplianceID = $SelectedID - $XMLDoc.Save($Agent.Path.ApplianceConfig) - Remove-Item $Agent.Path.ApplianceConfigBackup -Force - ### Replace the Server IPs in the Server Configuration - [Xml] $XMLDoc = Get-Content $Agent.Path.ServerConfig - $XMLDoc.ServerConfig.ServerIP = $Config.NCServerAddress - $XMLDoc.ServerConfig.BackupServerIP = $Config.NCServerAddress - $XMLDoc.Save($Agent.Path.ServerConfig) - Remove-Item $Agent.Path.ServerConfigBackup -Force - return $true -} - -function FixStartupType -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - $Agent.Services.Data.Keys | - Sort-Object | - ForEach-Object { - $CurrentService = $_ - ### Ensure Service Startup is Configured Properly - & SC.EXE CONFIG "$CurrentService" START= $Config.ServiceRepairString >$null 2>$null - # Fail the Repair if Service Configuration is Unsuccessful - $RepairResult = - if ($RepairResult -ne $false) - { $LASTEXITCODE -eq 0 } else { $RepairResult } - } - return $RepairResult -} - -function FixFailureBehavior -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - $Agent.Services.Data.Keys | - Sort-Object | - ForEach-Object { - $CurrentService = $_ - ### Ensure Service Failure Behavior is Configured Properly - # Build Command String - $ActionsPart = @( - @("A", "B", "C") | - ForEach-Object { - $CurrentAction = $_ - @($Config.$("ServiceAction" + $CurrentAction), $Config.$("ServiceDelay" + $CurrentAction)) -join '/' - } - ) -join '/' - # Configure the Service - if ($null -ne $Config.ServiceCommand) - { & SC.EXE FAILURE "$CurrentService" ACTIONS= $ActionsPart RESET= $Config.ServiceReset COMMAND= $Config.ServiceCommand >$null 2>$null } - else - { & SC.EXE FAILURE "$CurrentService" ACTIONS= $ActionsPart RESET= $Config.ServiceReset >$null 2>$null } - # Fail the Repair if Service Configuration is Unsuccessful - $RepairResult = - if ($RepairResult -ne $false) - { $LASTEXITCODE -eq 0 } else { $RepairResult } - } - return $RepairResult -} - -function RepairAgent -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - $Script.Execution.ScriptAction = "Performing Applicable Repairs" - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Function Body - ############################### - ### Check if Installation is Required - $Out = - switch ($Agent.Health.AgentStatus) - { - $SC.ApplianceStatus.G - { ("The " + $NC.Products.Agent.Name + " is currently not installed."); break } - $SC.ApplianceStatus.F - { ("The current " + $NC.Products.Agent.Name + " installation is damaged and must be re-installed."); break } - $SC.ApplianceStatus.E - { ("The current " + $NC.Products.Agent.Name + " installation is not authenticating with the Partner N-Central Server and must be re-installed."); break } - { $Agent.Health.VersionCorrect -eq $false } - { ("The current " + $NC.Products.Agent.Name + " installation is out of date and must be upgraded."); break } - } - if ($null -ne $Out) - { # Skip Repair Sequence - $Script.Sequence.Status[-1] = $SC.SequenceStatus.D - Log I 0 ("The Repair Sequence was skipped. " + $Out) - } - else - { ### Select Repairs to Perform - switch ($Agent.Health) - { - { $_.ApplianceIDValid -eq $false } - { $Repair.Required += @("A") } - { $_.ServicesStartupCorrect -eq $false } - { $Repair.Required += @("B") } - { $_.ServicesBehaviorCorrect -eq $false } - { $Repair.Required += @("C") } - { @($_.ProcessesRunning, $_.ServicesRunning) -contains $false } - { $Repair.Required += @("D") } - } - if ($null -eq $($Repair.Required)) - { # ERROR - Repairs Required but None Selected - Quit 101 - } - else - { ### Perform Selected Repairs - $Repair.Required | - ForEach-Object { - switch ($_) - { - "A" - { # Repair Appliance ID - $Repair.Results.$($SC.Repairs.$_.Name) = FixOrphanedAppliance - } - "B" - { # Repair Service Startup Type - $Repair.Results.$($SC.Repairs.$_.Name) = FixStartupType - } - "C" - { # Repair Service Failure Behavior - $Repair.Results.$($SC.Repairs.$_.Name) = FixFailureBehavior - } - "D" - { # Restart Agent Services/Processes - $Repair.Results.$($SC.Repairs.$_.Name) = FixServices - } - } - } - ### Perform Post-Repair Actions - # Determine Required Actions - $Repair.Required | - ForEach-Object { - $PostRepairActions += @( - switch ($SC.Repairs.$_.PostRepairAction) - { - $null - { # No Post-Repair Action - break - } - Default - { # Add the Action to the List - $_ - break - } - } - ) - } - # Perform Required Actions - if ($PostRepairActions -contains $SC.RepairActions.A) - { # Skip Recovery Actions since Installation is Required - $Script.Sequence.Status[-1] = $SC.SequenceStatus.E - } - else - { - if ($PostRepairActions -contains $SC.RepairActions.B) - { # Restart the Agent Services - $Repair.Results.$($SC.Repairs.PostRepair.Name) = FixServices -Restart - } - ### Perform Recovery Actions if Required - # Determine Required Actions - $Repair.Results.Keys | - ForEach-Object { - $CurrentRepair = $_ - # Perform Recovery Actions only if the Repair Failed - if ($Repair.Results.$CurrentRepair -eq $false) - { - $RecoveryActions += @( - switch ( - $SC.Repairs.GetEnumerator() | - Where-Object { $_.Value.Name -eq $CurrentRepair } | - ForEach-Object { $_.Value.RecoveryAction } - ) - { - $null - { # No Recovery Action - break - } - Default - { # Add the Action to the List - $_ - break - } - } - ) - } - } - # Perform Required Actions - if ($RecoveryActions -contains $SC.RepairActions.A) - { # Skip Remaining Recovery Actions since Installation is Required - $Script.Sequence.Status[-1] = $SC.SequenceStatus.E - } - else - { - if ($RecoveryActions -contains $SC.RepairActions.B) - { # Restart the Agent Services - $Repair.Results.$($SC.Repairs.Recovery.Name) = FixServices -Restart - } - } - } - } - ### Re-Check Agent Status After Repairs - DiagnoseAgent -NoLog -NoServerCheck - ### Summarize Repair Results - # Update Registry Values - $Script.Execution.AgentLastRepaired = Get-Date -UFormat $SC.DateFormat.Full - WriteKey $Script.Results.ScriptRepairKey $Repair.Results - # Log Detailed Repair Results - $Out = @("The following Repairs were attempted by the Script:") - $Out += - $Repair.Results.Keys | - Sort-Object | - ForEach-Object { - $CurrentRepair = $_ - $RepairStatus = - switch ($Repair.Results.$CurrentRepair) - { - $true - { "SUCCESS"; break } - $false - { "FAILURE"; break } - } - @($CurrentRepair + " - " + $RepairStatus) - } - Log I 0 $Out - ### Determine Overall Repair Outcome - switch ($Agent.Health.AgentStatus) - { - $SC.ApplianceStatus.A - { # Complete Repair Sequence and Exit - $Out = @( - switch ($Repair.Results) - { - { @($_.$($SC.Repairs.PostRepair.Name), $_.$($SC.Repairs.Recovery.Name)) -contains $false } - { # Errors in Post-Repair/Recovery - "Some minor issues were encountered during Repairs, however the Script has found them to be resolved.", - "Possible reasons for this result may include:", - ("- A request to one or more " + $NC.Products.Agent.Name + " Services timed out"), - ("- A pre-existing operation was in place on one or more " + $NC.Products.Agent.Name + " Services") - break - } - Default - { # Agent Successfully Repaired without Issue - "The existing " + $NC.Products.Agent.Name + " installation was repaired successfully, without the need for installation." - break - } - } - ) - Log I 0 $Out - Log -EndSequence -Code 0 -Exit - } - Default - { - $Out = @( - switch ($Script.Sequence.Status[-1]) - { - $SC.SequenceStatus.C - { # Fail Repair Sequence - $Script.Sequence.Status[-1] = $SC.SequenceStatus.F - switch ($Repair.Results) - { - { # Errors in Post-Repair/Recovery - @( - $_.$($SC.Repairs.PostRepair.Name), - $_.$($SC.Repairs.Recovery.Name) - ) -contains $false - } - { # Require Installation - ("One or more Post-Repair or Recovery Actions failed. The current " + $NC.Products.Agent.Name + " must be re-installed.") - break - } - Default - { # Error(s) in Standard Repair - ("Standard Repairs failed to fully correct all identified issues. The current " + $NC.Products.Agent.Name + " must be re-installed.") - break - } - } - } - $SC.SequenceStatus.E - { # Abort Repair Sequence and Require an Install - ("The Repair Sequence was aborted. The current " + $NC.Products.Agent.Name + " cannot be fixed with standard repairs and must be re-installed.") - break - } - } - ) - Log I 0 $Out - } - } - } -} - -### INSTALLATION FUNCTIONS -############################### - -function SelectInstallers -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - ### Validate Potential Installation Sources - $Install.NETLOGONAccess = - if ($Device.IsDomainJoined -eq $true) - { - try - { Test-Path ("\\" + $Device.FQDN + "\NETLOGON") -PathType Container } - catch [System.IO.IOException] - { $false } - } - else { $false } - # Check each Source for Required Installers - $Install.Sources.Keys | - Sort-Object | - ForEach-Object { - $CurrentSourceName = $_ - $CurrentSource = $Install.Sources.$_ - if (($CurrentSourceName -eq "Network") -and ($Install.NETLOGONAccess -eq $false)) - { - $CurrentSource.Available = $false - $CurrentSource.AgentFound = $false - $CurrentSource.AgentValid = $false - $CurrentSource.NETFound = $false - $CurrentSource.NETValid = $false - } - else - { - $CurrentSource.Available = Test-Path ($CurrentSource.Path) -PathType Container - $CurrentSource.AgentFound = - if ($CurrentSource.Available -eq $true) - { # Verify Agent Installer Exists - if (Test-Path ($CurrentSource.Path + "\" + $Config.AgentFile) -PathType Leaf) - { - $true - $AgentFile = Get-Item ($CurrentSource.Path + "\" + $Config.AgentFile) 2>$null - } else { $false } - } else { $false } - $CurrentSource.AgentValid = - if ($CurrentSource.AgentFound -eq $true) - { # Validate Agent Installer Version - $CurrentSource.$($AgentFile.Name) = [Version] (ValidateVersion $($AgentFile.VersionInfo.FileVersion)) - if ( - (($CurrentSource.$($AgentFile.Name)) -ne $Config.AgentFileVersion) -or - (($CurrentSource.$($AgentFile.Name)) -eq "0.0.0.0") - ) - { $false } else { $true } - } else { $false } - $CurrentSource.NETFound = - if ($CurrentSource.Available -eq $true) - { # Verify .NET Installer Exists - if (Test-Path ($CurrentSource.Path + "\" + $Config.NETFile) -PathType Leaf) - { - $true - $NETFile = Get-Item ($CurrentSource.Path + "\" + $Config.NETFile) 2>$null - } else { $false } - } else { $false } - $CurrentSource.NETValid = - if ($CurrentSource.NETFound -eq $true) - { # Validate .NET Installer Version - $CurrentSource.$($NETFile.Name) = [Version] (ValidateVersion $($NETFile.VersionInfo.FileVersion)) - if ( - (($CurrentSource.$($NETFile.Name)) -ne $Config.NETFileVersion) -or - (($CurrentSource.$($NETFile.Name)) -eq "0.0.0.0") - ) - { $false } else { $true } - } else { $false } - } - # Choose the Best Source for Each Installer - if (@(0, $null) -contains $Install.ChosenAgent.Count) - { # Select the Agent Source - if ($CurrentSource.AgentValid -eq $true) - { - $Install.Sources.ChosenAgent = $CurrentSource.Path - $Install.ChosenAgent = @{ - "FileName" = $AgentFile.Name - "InstallPath" = @($Script.Path.InstallDrop, $AgentFile.Name) -join '\' - "Path" = @($Install.Sources.ChosenAgent, $AgentFile.Name) -join '\' - "Version" = $CurrentSource.$($AgentFile.Name) - } - } - } - if (@(0, $null) -contains $Install.ChosenNET.Count) - { # Select the .NET Source - if ($CurrentSource.NETValid -eq $true) - { - $Install.Sources.ChosenNET = $CurrentSource.Path - $Install.ChosenNET = @{ - "FileName" = $NETFile.Name - "InstallPath" = @($Script.Path.InstallDrop, $NETFile.Name) -join '\' - "Path" = @($Install.Sources.ChosenNET, $NETFile.Name) -join '\' - "Version" = $CurrentSource.$($NETFile.Name) - } - } - } - } - ### Warn on Domain Joined Device with no Domain Access - if (($Device.IsDomainJoined -eq $true) -and ($Install.NETLOGONAccess -eq $false)) - { Log W 0 ("Device is joined to the [" + $Device.FQDN + "] Domain, but either cannot currently reach a Domain Controller, or does not have access to the NETLOGON Folder.") } - ### Log the Chosen Install Kits - $Install.Results.SelectedAgentKit = - switch ($Install.Sources.ChosenAgent) - { - $Install.Sources.Demand.Path - { $SC.InstallKit.C; break } - $Install.Sources.Network.Path - { $SC.InstallKit.B; break } - Default - { $SC.InstallKit.A; break } - } - $Install.Results.SelectedNETKit = - switch ($Install.Sources.ChosenNET) - { - $Install.Sources.Demand.Path - { $SC.InstallKit.C; break } - $Install.Sources.Network.Path - { $SC.InstallKit.B; break } - Default - { $SC.InstallKit.A; break } - } - WriteKey $Script.Results.ScriptInstallKey $Install.Results - ### Verify that Valid Installers were Found - $Available = - $Install.Sources.GetEnumerator() | - ForEach-Object { $_.Value.Available } - $AgentFound = - $Install.Sources.GetEnumerator() | - ForEach-Object { $_.Value.AgentFound } - $NETFound = - $Install.Sources.GetEnumerator() | - ForEach-Object { $_.Value.NETFound } - if (($null -ne $Install.ChosenAgent) -and ($null -ne $Install.ChosenNET)) - { Log I 0 ("Verified the correct Windows Installer versions for the " + $NC.Products.Agent.Name + " (" + $Install.ChosenAgent.Version + ") and .NET Framework (" + $Install.ChosenNET.Version + ") are available to the Script.") } - else - { # Display Required Installers - $Out = @("The following Installer(s) are required by the Script in order to perform Installation Repairs on this system:") - $Out += - ($Config.AgentFile + " Version - " + $Config.AgentVersion + " (Windows " + $Config.AgentFileVersion + ")"), - ($Config.NETFile + " Version - " + (ValidateVersion $Config.NETVersion 2) + " (Windows " + (ValidateVersion $Config.NETFileVersion 2) + ")"), - "" - if ($Available -notcontains $true) - { # No Installers Available from any Source - $Out += @("No Installation Sources were available to the Script. Make sure that the relevant , , and values in the Partner Configuration are correct and that the locations are available in the Deployment Package.") - $ExitCode = 3 - } - else - { - if ( - ($AgentFound -contains $true) -and - ($NETFound -contains $true) - ) - { # Installer Version Mismatch - $Out += @("The following Installer(s) were found, but do not match the Version required by the Partner Configuration:") - switch ($Install.ChosenAgent.Version) - { - { $null -ne $_ } - { # Valid Installer Found - break - } - Default - { # Collect Available Installer Paths/Versions - $Install.Sources.GetEnumerator() | - Where-Object { $_.Name -notlike "Chosen*" } | - ForEach-Object { - $AgentVersion = $_.Value.$($Config.AgentFile) - if ($null -ne $AgentVersion) - { - $DisplayVersion = - if ($AgentVersion -eq "0.0.0.0") - { " - No Discovered Version" } else { (" Version - (Windows " + $AgentVersion + ")") } - $Out += @($Config.AgentFile + $DisplayVersion + " at " + $_.Value.Path) - } - } - $Values += @("", "") - break - } - } - switch ($Install.ChosenNET.Version) - { - { $null -ne $_ } - { # Valid Installer Found - break - } - Default - { # Collect Available Installer Paths/Versions - $Install.Sources.GetEnumerator() | - Where-Object { $_.Name -notlike "Chosen*" } | - ForEach-Object { - $NETVersion = $_.Value.$($Config.NETFile) - if ($null -ne $NETVersion) - { - $DisplayVersion = - if ($NETVersion -eq "0.0.0.0") - { " - No Discovered Version" } else { (" Version - (Windows " + $NETVersion + ")") } - $Out += @($Config.NETFile + $DisplayVersion + " at " + $_.Value.Path) - } - } - $Values += @("", "") - break - } - } - $Out += - "`nPlease update one of the following to the appropriate Version:", - "- The Installer(s) listed above at their respective locations", - "- The relevant values in the Partner Configuration:" - $Out += @($Values) - $ExitCode = 5 - } - else - { # Installer(s) Missing - $Out += @( - if (($AgentFound -notcontains $true) -and ($NETFound -notcontains $true)) - { "No Installers were found by the Script." } - else - { - "The following Installer(s) were not found by the Script, or the name does not match the Partner Configuration:" - if ($AgentFound -notcontains $true) - { - $Config.AgentFile - $MissingLocations += @($Install.Sources.ChosenAgent) - } - if ($NETFound -notcontains $true) - { - $Config.NETFile - $MissingLocations += @($Install.Sources.ChosenNET) - } - } - ) - $Out += - "`nVerify that the Installer(s) exist at:", - $MissingLocations, - "`nAlso verify that the relevant and values in the Partner Configuration are correct." - $ExitCode = 4 - } - } - Log E $ExitCode $Out -Exit - } -} - -function GetInstallMethods -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - ### Get Potential Values for Installation - $Values = @( - $Agent.Appliance.ActivationKey, - $Agent.Appliance.SiteID, - $Agent.History.ActivationKey, - $Agent.History.SiteID, - $Script.CustomerID, - $Agent.History.ScriptSiteID - ) - ### Populate Method Tables with Available Values - for ($i = 0; $i -lt $Values.Count; $i++) - { # Populate Method Tables - $Install.MethodData.$(AlphaValue $i) = @{ - "Attempts" = 0 - "Available" = - if ( - ($Agent.Health.AgentStatus -eq $SC.ApplianceStatus.E) -and - ($i -lt 4) - ) - { # Only use Script Customer ID for Takeover Installations - $false - } else { $null -ne $Values[$i] } - "Failed" = $false - "FailedAttempts" = 0 - "MaxAttempts" = $SC.InstallMethods.Attempts.$(AlphaValue $i) - "Name" = $SC.InstallMethods.Names.$(AlphaValue $i) - "Parameter" = - switch ($Values[$i]) - { - { $_ -match $NC.Validation.CustomerID } - { $NC.InstallParameters.B; break } - { $_ -match $NC.Validation.ActivationKey.Encoded } - { $NC.InstallParameters.A; break } - Default - { $null; break } - } - "Value" = $Values[$i] - } - # Populate Results Table only for Available Methods - if ($Install.MethodData.$(AlphaValue $i).Available -eq $true) - { - $Install.MethodResults.$(AlphaValue $i) = @{ - "Method" = $SC.InstallMethods.Names.$(AlphaValue $i) - "MethodAttempts" = 0 - "MethodSuccessful" = $null - } - } - } -} - -function MethodSummary -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - $Script.Execution.ScriptAction = "Summarizing Installation Results" - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Function Body - ############################### - # Update Registry Values - $Script.Execution.AgentLastInstalled = Get-Date -UFormat $SC.DateFormat.Full - WriteKey $Script.Results.ScriptInstallKey ($Install.Results + $Install.MethodResults.$($Install.ChosenMethod.Method)) - # Log Detailed Installation Results - $Out = @("The following Installation attempts were made on the system:") - $Out += - $Install.MethodResults.GetEnumerator() | - Where-Object { $_.Value.MethodAttempts -gt 0 } | - Select-Object -ExpandProperty Name | - Sort-Object | - ForEach-Object { - $m = $_ - $MethodStatus = - switch ($Install.MethodResults.$m.MethodSuccessful) - { - $true - { "SUCCESS"; break } - $false - { "FAILURE"; break } - } - $AttemptDisplayValue = - if ($Install.MethodResults.$m.MethodAttempts -eq 1) - { "Attempt" } else { "Attempts" } - @( - $Install.MethodResults.$m.Method, "-", - $Install.MethodResults.$m.MethodAttempts, $AttemptDisplayValue, "-", - $MethodStatus - ) -join ' ' - } - Log I 0 $Out -} - -function SelectInstallMethod -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - ### Fail Current Method if Limit Exceeded - if ($null -ne $Install.ChosenMethod.Method) - { - $MethodData = $($Install.MethodData.$($Install.ChosenMethod.Method)) - $MethodResults = $($Install.MethodResults.$($Install.ChosenMethod.Method)) - if ($Install.ChosenMethod.FailedAttempts -ge $Install.ChosenMethod.MaxAttempts) - { # Fail the Method - $MethodData.Failed = $true - $MethodResults.MethodAttempts = $Install.ChosenMethod.Attempts - $MethodResults.MethodSuccessful = $false - } - } - ### Select the Next Install Method - for ($i = 0; $i -lt $Install.MethodData.Count; $i++) - { - $MethodData = $($Install.MethodData.$(AlphaValue $i)) - if ( - ($MethodData.Available -eq $true) -and - ($MethodData.Failed -eq $false) - ) - { # Check if a Different Method was Chosen than Previously - if ($Install.ChosenMethod.Method -ne (AlphaValue $i)) - { # Initialize New Method - Reset Attempts - $Install.ChosenMethod = $MethodData - $Install.ChosenMethod.Method = AlphaValue $i - } - # Begin a New Attempt - $Install.ChosenMethod.Attempts++ - # Set Selection Flag - $MethodChosen = $true - break - } - } - if ($MethodChosen -ne $true) - { # ERROR - No Installation Methods Remaining - MethodSummary - $Out = - ("All available Methods and Attempts to install the " + $NC.Products.Agent.Name + " were unsuccessful.`n"), - ("Review the Event Log for any entries made by the " + $NC.Products.Agent.InstallerName + " Event Source for more details.") - Log E 12 $Out -Exit - } -} - -function UpdateHistory -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - # Update Configuration History File - $LastUpdate = - if ($Agent.History.Count -gt 0) - { $Agent.History } else { @{} } - $Agent.Appliance.GetEnumerator() | - ForEach-Object { - if ($null -ne $_.Value) - { $LastUpdate.$($_.Name) = $_.Value } - } - $LastUpdate.HistoryUpdated = Get-Date -UFormat $SC.DateFormat.Full - # Retain Customer IDs that were Successful in Activating the Agent - if ( - ($null -ne $Script.CustomerID) -and - ($Install.ChosenMethod.Value -eq $Script.CustomerID) - ) - { $LastUpdate.ScriptSiteID = $Script.CustomerID } - WriteXML $Agent.Path.History "Config" $LastUpdate -} - -function CheckMSIService -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - # Check if an Installation is in Progress - for ($i = 0; $i -lt ($Config.InstallTimeoutPeriod * 6); $i++) - { - $MSI_IP = ((Get-Process -Name "msiexec" -ErrorAction SilentlyContinue).Count -gt 1) - if ($MSI_IP -eq $false) - { break } else { Start-Sleep 10 } - } - # Update Optional Counter if Timeout is Reached - if ($i -ge ($Config.InstallTimeoutPeriod * 6)) - { # Exit - Windows Installer Service Unavailable - $Out = ( - "The Windows Installer Service has been unavailable for the timeout period of " + - $Config.InstallTimeoutPeriod + " minutes.`n`n" + - "This could be due to an Installer that is requesting user input to continue. " - ) - $Out += - switch ($Script.Execution.ScriptMode) - { - $SC.ExecutionMode.A - { "Run the Script again to re-attempt installation."; break } - $SC.ExecutionMode.B - { "Installation will be re-attempted at the next Device boot."; break } - } - $Out += " If the problem persists, consider rebooting the Device to clear any pending install operations." - Log E 9 $Out - } -} - -function InstallNET -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - $Script.Execution.ScriptAction = "Installing Prerequisite .NET Framework" - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Function Body - ############################### - ### Retrieve the Chosen Installer for Local Install - # Remove an Old Installer if it Exists - ValidateItem $Install.ChosenNET.InstallPath -RemoveItem >$null - # Transfer the Installer - try - { Copy-Item $Install.ChosenNET.Path $Install.ChosenNET.InstallPath -Force 2>&1 -ErrorAction Stop } - catch - { # ERROR - File Transfer Failed - $ExceptionInfo = $_.Exception - $InvocationInfo = $_.InvocationInfo - CatchError 102 -Exit - } - # .NET Framework Install Properties - $NET = New-Object System.Diagnostics.ProcessStartInfo ($env:windir + "\system32\cmd.exe") - $NET.UseShellExecute = $false - $NET.CreateNoWindow = $true - $NET.Arguments = ('/C "' + $Install.ChosenNET.InstallPath + '" /q /norestart') - # Check Availability of Windows Installer - CheckMSIService - # Install .NET Framework - [System.Diagnostics.Process]::Start($NET).WaitForExit() >$null - # Re-Check .NET Framework Version - GetNETVersion - $Out = ( - ".NET Framework Version " + (ValidateVersion $Config.NETVersion 2) + - " is required prior to installation of " + $NC.Products.Agent.Name + - " Version " + $Config.AgentVersion - ) - if ($Device.NETProduct -lt (ValidateVersion $Config.NETVersion 2)) - { # Exit - .NET Framework Installation Failed - $Out += ". An error occurred during installation.`n`nReview the Event Log for relevant details." - Log E 10 $Out -Exit - } - $Out += " and was installed successfully." - Log I 0 $Out -} - -function RemoveAgent -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - ### Function Body - ############################### - # Agent Removal Properties - $REM = New-Object System.Diagnostics.ProcessStartInfo ($env:windir + "\system32\cmd.exe") - $REM.UseShellExecute = $false - $REM.CreateNoWindow = $true - $REM.Arguments = ('/C "' + $Agent.Registry.UninstallString + ' /QN /NORESTART"') - # Check Availability of Windows Installer - CheckMSIService - # Remove the Existing Agent - FixServices -Disable - [System.Diagnostics.Process]::Start($REM).WaitForExit() >$null - # Verify Removal was Successful - DiagnoseAgent -NoLog -NoServerCheck - if ($Agent.Health.Installed -eq $true) - { # Exit - Agent Removal Failed - $Out = ( - "MSI Removal of the existing " + $NC.Products.Agent.Name + " failed. " + - "Manual forcible removal is required for the Script to continue." - ) - Log E 11 $Out -Exit - } - else - { $Install.ExistingAgentRemoved = $true } -} - -function VerifyPrerequisites -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - $Script.Execution.ScriptAction = "Verifying Installation Requirements" - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Function Body - ############################### - ### Check Connectivity to Partner Server - if ($Install.NCServerAccess -eq $false) - { # Exit - Installer will Fail to Authenticate with Server - $Out = - ("The Device is currently unable to reliably reach the " + $NC.Products.NCServer.Name + ". Installation attempts will fail authentication.`n"), - "This may be caused by lack of Internet connectivity, a poor connection, or DNS is unavailable or unable to resolve the address.", - "If this issue persists, verify the value in the Partner Configuration is correct." - Log E 6 $Out -Exit - } - ### Validate and Choose from Available Installers - SelectInstallers - ### Check if the Script has Sufficient Info to Install the Agent - GetInstallMethods - # Verify at least one Method is Available for Installation - $MethodsAvailable = ( - $Install.MethodData.Keys | - ForEach-Object { $Install.MethodData.$_.Available } - ) -contains $true - if ($MethodsAvailable -ne $true) - { # Exit - No Available Installation Methods - $Out = - if ($null -eq $CustomerID) - { - @("An " + $NC.Products.Agent.IDName + " was not provided to the Script and is required for Installation.`n") - $ExitCode = 7 - } - else - { - @("The " + $NC.Products.Agent.IDName + " provided to the Script [" + $CustomerID + "] is invalid. A valid Customer ID is required for Installation.`n") - $ExitCode = 8 - } - $Out += - switch ($Script.Execution.ScriptMode) - { - $SC.ExecutionMode.A - { @("For On-Demand Deployment - Please specify a valid " + $NC.Products.Agent.IDName + " when running " + $SC.Names.LauncherFile + "."); break } - $SC.ExecutionMode.B - { @("For Group Policy Deployment - Verify the " + $NC.Products.Agent.IDName + " is free of typographical errors, and is the only item present in the Parameters field for the GPO."); break } - } - Log E $ExitCode $Out -Exit - } - ### Verify the Required Version of .NET Framework is Installed - # Verify the Working Install Folder - $DropFolderExists = ValidateItem $Script.Path.InstallDrop -Folder - if ($DropFolderExists -eq $false) - { # ERROR - Transfer Folder Creation Failed - Quit 104 - } - # Get the Currently Installed Product/Version - GetNETVersion - # Install .NET Framework if Required - if ($Device.NETProduct -lt (ValidateVersion $Config.NETVersion 2)) - { InstallNET } - ### Determine Required Installation Action - $Install.RequiredAction = - if ($Agent.Health.Installed -eq $true) - { - if (($Agent.Health.VersionCorrect -eq $true) -or ($Agent.Health.AssignedToPartnerServer -eq $false)) - { $SC.InstallActions.C } else { $SC.InstallActions.B } - } else { $SC.InstallActions.A } -} - -function InstallAgent -{ ### Definitions - ############################### - # Function Info - $Function.LineNumber = $MyInvocation.ScriptLineNumber - $Function.Name = '{0}' -f $MyInvocation.MyCommand - # Execution Info - $Script.Execution.ScriptAction = - switch ($Install.RequiredAction) - { - $SC.InstallActions.A - { "Installing New Agent"; break } - $SC.InstallActions.B - { "Upgrading Existing Agent Installation"; break } - $SC.InstallActions.C - { "Replacing Existing Agent Installation"; break } - } - WriteKey $Script.Results.ScriptKey $Script.Execution - ### Function Body - ############################### - ### Attempt Agent Installation - for ( - $Install.ChosenMethod.FailedAttempts = 0 - $true # SelectInstallMethod Function acts as the Loop Condition - $Install.ChosenMethod.FailedAttempts++ - ) - { ### Choose the Best Install Method - SelectInstallMethod - ### Build the Install String - # Add N-Central Server Parameters - $Install.AgentString = @( - '/S /V" /qn', - (@($NC.InstallParameters.D, $Config.NCServerAddress) -join '='), - (@($NC.InstallParameters.E, "443") -join '='), - (@($NC.InstallParameters.F, "HTTPS") -join '=') - ) -join ' ' - # Add Install Parameters - $Install.AgentString += (' ' + (@($Install.ChosenMethod.Parameter, $Install.ChosenMethod.Value) -join '=')) - if ($Install.ChosenMethod.Parameter -eq $NC.InstallParameters.B) - { $Install.AgentString += (' ' + (@($NC.InstallParameters.C, "1") -join '=')) } - # Add Proxy String if it Exists - if ($null -ne $Config.ProxyString) - { $Install.AgentString += (' ' + (@($NC.InstallParameters.G, $Config.ProxyString) -join '=')) } - # Complete the String - $Install.AgentString += '"' - ### Set Agent Install Properties - $INST = New-Object System.Diagnostics.ProcessStartInfo ($Install.ChosenAgent.InstallPath) - $INST.UseShellExecute = $false - $INST.CreateNoWindow = $true - $INST.Arguments = $Install.AgentString - ### Perform Required Installation Actions - # Ensure MMC is not currently in use on the System (Prevents Service Deletion) - foreach ($p in @("mmc", "taskmgr")) - { Get-Process -Name $p 2>$null | Stop-Process -Force 2>$null } - # Remove the Existing Agent if Required - if ( - (@($SC.InstallActions.B, $SC.InstallActions.C) -contains $Install.RequiredAction) -and - ($Agent.Health.Installed -eq $true) -and - ($Install.ExistingAgentRemoved -ne $true) - ) - { RemoveAgent } - ### Retrieve the Chosen Installer for Local Install - # Remove an Old Installer if it Exists - ValidateItem $Install.ChosenAgent.InstallPath -RemoveItem >$null - # Transfer the Installer - try - { Copy-Item $Install.ChosenAgent.Path $Install.ChosenAgent.InstallPath -Force 2>&1 -ErrorAction Stop } - catch - { - $ExceptionInfo = $_.Exception - $InvocationInfo = $_.InvocationInfo - CatchError 102 -Exit - } - # Check Availability of Windows Installer - CheckMSIService - # Install the Required Agent - $Proc = [System.Diagnostics.Process]::Start($INST) - $Proc.WaitForExit() - $Install.Results.InstallExitCode = $Proc.ExitCode - ### Verify the Installation Status - if ($Install.Results.InstallExitCode -eq 0) - { # Wait for the Agent Services to Start - for ( - $i = 0 - ( - ($i -lt 12) -and - ( - ($Agent.Health.ServicesExist -eq $false) -or - ($Agent.Health.ServicesRunning -eq $false) - ) - ) - $i++ - ) - { QueryServices; Start-Sleep 10 } - # Run a Service Repair if the Agent Services Haven't Started After Install - if ($i -ge 12) - { $ServicesStarted = FixServices } - if ( - (($i -ge 12) -and ($ServicesStarted -eq $true)) -or - ($i -lt 12) - ) - { # Verify Services are Running Post-Install - $Services = VerifyServices - # Enforce Service Startup Type - $Startup = FixStartupType - # Enforce Service Behavior - $Behavior = FixFailureBehavior - $Install.Results.VerifiedServices = ($Services -eq $true) -and ($Startup -eq $true) -and ($Behavior -eq $true) - # Re-Check Agent Health Post-Install - DiagnoseAgent - if (($Agent.Health.Installed -eq $true) -and ($Agent.Health.VersionCorrect -eq $true)) - { - $Install.Results.VerifiedStatus = $true - $Install.MethodResults.$($Install.ChosenMethod.Method).MethodAttempts = $Install.ChosenMethod.Attempts - $Install.MethodResults.$($Install.ChosenMethod.Method).MethodSuccessful = $true - break - } - } - } - } - ### Summarize Installation Results - # Update Historical Configuration - UpdateHistory - # Summarize Methods/Attempts Taken - MethodSummary - ### Report Overall Installation Status - if ($Agent.Health.AgentStatus -ne $SC.ApplianceStatus.A) - { # Warn that Repairs May still be Needed - $Out = @( - $NC.Products.Agent.Name, "Version", $Config.AgentVersion, - "was installed successfully, however, some items may still require Repair as indicated above. " - ) -join ' ' - $Out += - switch ($Script.Execution.ScriptMode) - { - $SC.ExecutionMode.A - { "Run the Script again to resolve these items."; break } - $SC.ExecutionMode.B - { "These items will be resolved at the next Device boot."; break } - } - Log W 0 $Out - } - else - { # Installation was a Complete Success - $Out = @( - $NC.Products.Agent.Name, "Version", $Config.AgentVersion, - "was installed successfully! No outstanding issues were detected with the new installation." - ) -join ' ' - Log I 0 $Out - } -} \ No newline at end of file diff --git a/Deployment Package/AGENT/PS2Install/XPTools/2003/support.cab b/Deployment Package/AGENT/PS2Install/XPTools/2003/support.cab deleted file mode 100644 index 0de2ff4..0000000 Binary files a/Deployment Package/AGENT/PS2Install/XPTools/2003/support.cab and /dev/null differ diff --git a/Deployment Package/AGENT/PS2Install/XPTools/2003/suptools.msi b/Deployment Package/AGENT/PS2Install/XPTools/2003/suptools.msi deleted file mode 100644 index 0d6c513..0000000 Binary files a/Deployment Package/AGENT/PS2Install/XPTools/2003/suptools.msi and /dev/null differ diff --git a/Deployment Package/AGENT/PS2Install/XPTools/XP/sup_pro.cab b/Deployment Package/AGENT/PS2Install/XPTools/XP/sup_pro.cab deleted file mode 100644 index 52481be..0000000 Binary files a/Deployment Package/AGENT/PS2Install/XPTools/XP/sup_pro.cab and /dev/null differ diff --git a/Deployment Package/AGENT/PS2Install/XPTools/XP/sup_srv.cab b/Deployment Package/AGENT/PS2Install/XPTools/XP/sup_srv.cab deleted file mode 100644 index 13c9edc..0000000 Binary files a/Deployment Package/AGENT/PS2Install/XPTools/XP/sup_srv.cab and /dev/null differ diff --git a/Deployment Package/AGENT/PS2Install/XPTools/XP/support.cab b/Deployment Package/AGENT/PS2Install/XPTools/XP/support.cab deleted file mode 100644 index e148e4f..0000000 Binary files a/Deployment Package/AGENT/PS2Install/XPTools/XP/support.cab and /dev/null differ diff --git a/Deployment Package/AGENT/PS2Install/XPTools/XP/suptools.msi b/Deployment Package/AGENT/PS2Install/XPTools/XP/suptools.msi deleted file mode 100644 index fa82a92..0000000 Binary files a/Deployment Package/AGENT/PS2Install/XPTools/XP/suptools.msi and /dev/null differ diff --git a/Deployment Package/AGENT/PartnerConfig.xml b/Deployment Package/AGENT/PartnerConfig.xml deleted file mode 100644 index f3d1414..0000000 --- a/Deployment Package/AGENT/PartnerConfig.xml +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - -My MSP -(888) MSP-4ALL -help@mymsp.com - - - - - 5 - - 5 - - - - - - 20 - - 20 - - - - - - - RESTART - RESTART - RESTART - - - 120 - 120 - 120 - - - - - 1440 - - Auto - - - - C:\AGENT - - AGENT - - - CurrentAgent - - NET4_5_2-Universal.exe - - 4.5.2 - - 4.5.51209.34209 - - WindowsAgentSetup.exe - - 12.0.1.118 - - 12.0.10118.0 - - - - LegacyAgent - - NET4_0-Universal.exe - - 4.0 - - 4.0.30319.1 - - WindowsAgentSetup.exe - - 11.0.0.1114 - - 11.0.2114.0 - - - - \ No newline at end of file diff --git a/DevelopmentAndDebugging.md b/DevelopmentAndDebugging.md new file mode 100644 index 0000000..e4fd0f9 --- /dev/null +++ b/DevelopmentAndDebugging.md @@ -0,0 +1,387 @@ +# Development and Debugging +## Overview +The PowerShell version of the InstallAgent package uses a highly structured, robust coding methodology with validation and logging at every step, these characteristics make it suitable for use in broad rollouts from small to enterprise businesses. + +While it has many positive attributes it can be difficult to approach when re-developing or debugging. So let's provide a high level view of how the script would run under normal circumstances and highlighting the important parts: + +![](media/debugging-image0.png) + +While this doesn't represent all the functions inside of the InstallAgent-Core.psm1, we will explore it more in depth. + +## Challenges and starting debugging +There are a number of challenges that you can run into when developing code for the InstallAgent package: +* The Installagent.ps1 and the folder structure is intended to self delete after running, as it's intended to be run temporarily from C:\Windows\Temp\AGPO after the LaunchInstaller.bat/ps1 copies it there from the Netlogon/Source folder. +* Functions in InstallAgent-Core.psm1 depend upon variables declared in InstallAgent.ps1 that exist in Script scope, this is opposed to functions that are passed passed parameters and all variables are private to it's scope +* Functions will populate a number of variables with hashtables, their fields names and types are not declared anywhere in code. + +The latter challanges are necessary artifacts caused by the constraints in PowerShell 2.0, to debug this code efficiently and cross reference variables it is necessary to use an IDE like Visual Studio Code or PowerShell ISE. + +A point to set a debug point to see all the variables/tables populated and in a state to install or upgrade, I recommend put a breakpoint on the call to **SelectInstallMethod** function inside of the **InstallAgent** function at around line 3107 at time of writing. + +Next either populate the PartnerConfig as outlined in the ReadMe.md and/or run the InstallAgent.ps1 with CustomerID/Token parameters, it's important to run the script with the dot source operator (period) and run it inside the [current session scope](https://devblogs.microsoft.com/powershell/powershell-constrained-language-mode-and-the-dot-source-operator/) + +`. .\InstallAgent.ps1 -CustomerID CustomerID -RegistrationToken RegistrationToken -LauncherPath C:\Repos\Agent\ -DebugMode` + +Also make sure the LauncherPath parameter has a trailing \ otherwise it will cause issues. Once done you can explore all the variables in memory, and run some of the built in debug commands that provide a Gridview of useful tables: +* DebugGetMethods +* DebugGetAppliance + +Another function available is **DebugGetProxyTokens**, this is used to resolve all Customer IDs inside applicable install method data through the **RequestAzWebProxyToken** function. + +# TBC in next branch pull + + + +## Discuss important functions +### GetInstallMethods function +The GetInstallMethods function is the key method through in which validated installation data from the different sources is checked one last time before it is populated into the $Install.MethodData Hashtable + +The first important part of this function is here: + +```powershell +$Values = @( + $Script.ActivationKey, + $Config.ActivationKey, + $Agent.History.ActivationKey, + $Agent.Appliance.SiteID, + ($Script.CustomerID), + "$($Script.CustomerID)|$($Script.RegistrationToken)", + "$($Agent.History.ScriptSiteID)|$($Agent.History.RegistrationToken)", + "$($Config.CustomerId)|$($Config.RegistrationToken)", + $Script.CustomerID, + $Config.CustomerId +) +``` +These values are then piped through the `for ($i = 0; $i -lt $Values.Count; $i++) { ... } ` loop where for each `$i` it generates a hashtable and assigns it through to the `$Install.MethodData` table. + +Where this can be confusing is that the assignment is done here `$Install.MethodData.$(AlphaValue $i)` there the `AlphaValue $i` function is called on `$i` to resolve it to the letter, eg. $i==1 is the letter A, $i==2 is B but this is how we populate out $Install.MethodData.(A,B,C,D ... ) keys. + +The hashtable assigned after that has a lot of logic in it, but results in the following hashtable being generated, in this case for the typical case of `$Install.Methodata.A`: +``` +Name Value +---- ----- +Parameter AGENTACTIVATIONKEY +FailedAttempts 0 +Type Activation Key: Token/AppId +MaxAttempts 1 +Value c2hvcnRlbmVkIGZvciB0aGlzIGRvY3VtZW50 +Name Activation Key : Token (Current Script) / Appliance ID (Existing Installation) +Attempts 0 +Failed False +Available True +``` +This is then iterated for each loop until all install methods for available sources is populated in A ->J to fill out the whole $Install.MethodData which if you view it with the DebugGetMethods + +![](media\debugging-image1.png) + +Let's dive into the next section of logic for the value of the `Available` key as it's is extremely specific and pulls from multiple pieces of data from different hashtables +```powershell +"Available" = +if ( + ($Agent.Health.AgentStatus -eq $SC.ApplianceStatus.E) -and + ($i -lt 5) +) { + # Only use Script Customer ID for Takeover Installations + $false +} +elseif (!$Config.IsAzNableAvailable -and $SC.InstallMethods.UsesAzProxy.(AlphaValue $i)) { + # If AzNableProxy configuration isn't available and method uses it... + $false +} +elseif ($Config.IsAzNableAvailable -and $SC.InstallMethods.Type.(AlphaValue $i) -eq $SC.InstallMethods.InstallTypes.B -and $Agent.Health.Installed -eq $false) { + # If the type is AzNableProxy, it is Activation Type install and the agent is not installed + $false +} +else { $null -ne $Values[$i] -and "|" -ne $Values[$i] -and $Values[$i] -notlike "*|" } +``` +Let's break this down to each if/else statement: +```powershell +if (($Agent.Health.AgentStatus -eq $SC.ApplianceStatus.E) -and ($i -lt 5)) {$false} +``` +If the `$Agent.Health.AgentStatus` which is a diangoed to be one of the following values in `$SC.ApplianceStatus` table in the `DiagnoseAgent` function called prior to the GetInstallMethods +```powershell +$SC.ApplianceStatus = @{ + "A" = "Optimal" + "B" = "Marginal" + "C" = "Orphaned" + "D" = "Disabled" + "E" = "Rogue / Competitor-Controlled" + "F" = "Corrupt" + "G" = "Missing" +} +``` +Is then determined to be the key value for `E` which is "Rogue / Competitor-Controlled", and the `$i` value is less than 5 which is the cut off point between Activation Key methods and Registration key methods. If all this is true then we assign `$false` to the `Available` key. + +Or in short if the agent is Rogue / Competitor-Controlled don't use Activation Key methods, always use Registration key methods; this is because the Activation Key method attempts to register with the N-Central server with a specific Appliance ID, if that Appliance ID is from a competitor N-Central server there would be a chance we'd register a device over the top of an existing device and cause a conflict. + +**Next section** +```powershell +elseif (!$Config.IsAzNableAvailable -and $SC.InstallMethods.UsesAzProxy.(AlphaValue $i)) { + # If AzNableProxy configuration isn't available and method uses it... + $false +} +``` +This checks if the `$Config.IsAzNableAvailable` variable is `$false` and that the `UsesAzProxy` value for that method, which is again looked up through `AlphaValue $i` +```powershell + "UsesAzProxy" = @{ + "A" = $false + "B" = $false + "C" = $false + "D" = $true + "E" = $true + "F" = $false + "G" = $false + "H" = $false + "I" = $true + "J" = $true + } +``` + +Or in short, if the information for the AzNableProxy is not in the PartnerConfig, and the current Method uses that service, then it sets `Available` as `$false` + +**Next section** +```powershell +elseif ($Config.IsAzNableAvailable -and $SC.InstallMethods.Type.(AlphaValue $i) -eq $SC.InstallMethods.InstallTypes.B -and $Agent.Health.Installed -eq $false) { + # If the type is AzNableProxy, it is Activation Type install and the agent is not installed + $false +} +``` + +This should be more familiar to us now, if the AzNableProxy information is in the PartnerConfig, and the current InstallType value that is in the following hashtable +```powershell +"InstallTypes" = @{ +"A" = "Activation Key: Token/AppId" +"B" = "Activation Key: AzNableProxy->Token/AppId" +"C" = "Registration Token: CustomerId/Token" +"D" = "Registration Token: CustomerId/AzNableProxy->Token" +} + ``` + +Is found to be of type `B` or the "Activation Key: AzNableProxy->Token/AppId" type and the Agent is not installed; then we set `Available` as `$false` + +Or in short, don't make the AzNableProxy type Activation Key install methods available if there is no agent to upgrade. + +**Finally:** +```powershell +else { $null -ne $Values[$i] -and "|" -ne $Values[$i] -and $Values[$i] -notlike "*|" } +``` +The first thing we should note here is this is an `else`, not an `elseif` so we're not evaluating this to determine *if* we run a code block, this *is* the code block we are running. This is fairly straight forward: +* If the value is not null and; +* the value is not a plain pipe, which is a value that we can get as it's a delimeter when constructing the `CustomerID|Token` string and; +* the value is not a CustomerID, followed by a pipe, then no token, again an artifact of constructing the `CustomerID|Token` that is later used in the `InstallAgent` function for Registration Key methods + +## DiagnoseAgent function +The DiagnoseAgent function is one of the larger and more complicated functions so we'll provide a high level overview of how this works: +* It initialises the `$Agent.Appliance` which holds the current appliance health and the `$Agent.Docs` variables where de-serialized versions of the ApplianceConfig.xml, the Install Log and a copy of the Agent install registry keys are placed while they are parsed and the health of the Agent determined. +* As the files and registry are loaded into the `$Agent.Docs` they are validated against regex expressions found under the `$SC.Validation` variable declared in the InstallAgent.ps1 file. If any part of the agent documents fail the regex test, the script will halt later on and provide information on the failed document. +* The next section then builds the Activation Key/Appliance ID from available parts if they're available, by building the activation key we can perform ugprades on existing installs while specify the appliance ID, reducing the chance of generic installs resulting in a database mismatch. +* The agent history file is updated with the token data and the health of the agent services are checked +* The ApplianceID and version of the agent is checked, if the agent version is less than the target version then it is flagged for an update +* Connectivity to the Partner N-Central server is checked, if it isn't available only limited repairs can be done as a connection to the N-Central server is required for installs or upgrades. +* A summary of the Agent health is placed in a hash table inside teh $Agent.Health.AgentStatus variable +* The agent status is logged for the final event log output +* If the agent is healthy and there is nothing to be done, the script ends, otherwise it proceeds to the next phases of repair or re-install + +## RequestAzWebProxyToken function +RequestAzWebProxyToken is new to 6.0.0 and leverages Kelvin Tegelaar's AzNableProxy to retrieve the registration token securely from your on N-Central server without exposing API keys. + +To retrieve this token the function will: +* Create a URI from the partner configured values in the variables `$Config.AznableProxyUri` and `$Config.AzNableAuthCode` +* A PowerShell 3.0 Invoke-Webrequest method is called to retrieve the registration token. While technically the script is meant to be PowerShell 2.0 compatible there is a broader discussion on the security of PowerShell 2.0 and the age of the OS being used if it is 2.0. While it is possible to use a `[System.Net.WebClient]` method to download the registration token, that would be associated with TLS1.0 and you would need to make a concious security decision to use that legacy protocol. +* The retrieved token is checked to determine if it is a valid GUID +* If the chosen install method is of a type that requests converting to an encoded install key, it is created, otherwise the token is provided raw to the `$Install.ChosenMethod.Value` +## Custom Modules +The default InstallAgent-Core.psm1 module provides the default behaviors, and now includes AzNableProxy token lookup by default. + +By using custom modules that override default functions you can achieve most any features you need for edge case deployments. This works because of the order in which the modules are loaded, for example the new `RequestAzWebProxyToken` has certain default behaviors that may not be suitable for your environment, by loading a custom module after the inital InstallAgent-Core.psm1 that has a function with the same name, the custom module you created will supercede the default `RequestAzWebProxyToken` + +Once you've created your new module/files, place it in the Agent\Lib folder and update the $SC.Names.LibraryFiles array in the InstallAgent.ps1 file. + +Examples of what can be achieved is illustrated with the following files within the Custom Library folder: + +GetCustomInstallMethodExamples.psm1 - Demonstrates how you can provide a custom Install Method information back to the correct Hashtable +CustomOverrideExample.psm1 - Demonstrates how you can +Add additional telemetry when calling the AzNableProxy GET function with a few extra lines of code +Deliver detailed telemetry on Exit or Failure with a few extra lines of code +## Appendices: Detail on example tables of importance +### $Config +The `$Config` table is the variable to which the values of the **PartnerConfig.xml** is assigned, this is a 1:1 mapping of the Keys and Values. + +|Key|Value| +| :- | :- | +|NCServerAddress|ncentral.mymsp.com| +|ServiceQueryString|Auto| +|AgentFile|WindowsAgentSetup.exe| +|NETFile|NET4\_5\_2-Universal.exe| +|AzNableAuthCode|c2hvcnRlbmVkIGZvciB0aGlzIGRvY3VtZW50 | +|ErrorContactInfo|My MSP information call 1800 123 456| +|AgentVersion|2020.1.5.425| +|PingTolerance|20| +|LocalFolder|C:\Windows\Security ThroughObscurity| +|ServiceStartup|Automatic| +|ServiceRepairString|Auto| +|ActivationKey|c2hvcnRlbmVkIGZvciB0aGlzIGRvY3VtZW50 | +|ServiceDelayA|0| +|NETVersion|4.5.2.0| +|NetworkFolder|Agent| +|ServiceDelayC|0| +|ServiceCommand|| +|ServiceReset|86400| +|ServiceActionB|RESTART| +|CustomerId|364| +|InstallFolder|CurrentAgent| +|NETFileVersion|4.5.51209.34209| +|RegistrationToken|f01ebde4-9cd2-46a1-9e5b-a41961c9c43b| +|AgentFileVersion|2020.1.50425.0| +|AzNableProxyUri|myazproxy.azurewebsites.net| +|PingCount|20| +|ServiceDelayB|120000| +|BootTimeWaitPeriod|0| +|ProxyString|| +|ServiceRequireDelay|FALSE| +|ServiceActionA|RESTART| +|IsAzNableAvailable|TRUE| +|InstallTimeoutPeriod|5| +|ServiceActionC|RESTART| + +### $Script + +The `$Script` hashtable is the root table for several other sub-hashtables such as the results, sequence and path, the more important vales here are `CustomerID` and `ActivationKey` that are passed through to the Method Data + +|Name|Value| +| :- | :- | +|Sequence|{Status, Order}| +|Results|{LauncherSource, ScriptSource, ScriptEventKey, ScriptInstallKey...}| +|RegistrationToken|f01ebde4-9cd2-46a1-9e5b-a41961c9c43b| +|Execution|{ScriptLastRan, ScriptAction, ScriptMode, ScriptVersion...}| +|CustomerID|123| +|Path|{InstallDrop, Library, TempFolder, PartnerFile}| +|ActivationKey|c2hvcnRlbmVkIGZvciB0aGlzIGRvY3VtZW50| +|Parameters|{[CustomerID, 123], [RegistrationToken, {GUID}],[LauncherPath,C:\temp\], [DebugMode, True]}| +|Invocation|C:\temp\InstallAgent.ps1| + +### $Device +The `$Device` stores the results of the `GetDeviceInfo` function, these values are retrieved early on to help identify the location of the agent, the .NET version and if that needs to updated prior to proceeding. It's at this stage we can also quit out if there is something about this version of Windows that is not compatible. As older OS's like Windows 8/8.1 and 2012 R2 are phased out the values can be used to exit before attempting install. + +|Key|Example Values| +| :- | :- | +|PF|C:\Program Files| +|ServerCore|FALSE| +|Name|MY-REAL-PC| +|IsSRV|FALSE| +|OSBuild|10.0.18363| +|Architecture|64-bit| +|IsBDC|FALSE| +|LastBootTime|12/02/2021 14:42| +|FQDN|WORKGROUP| +|IsWKST|TRUE| +|IsDC|FALSE| +|NETProduct|4.8.0.0| +|Hostname|MY-REAL-PC| +|NETDisplayProduct|4.8.0.0| +|OSName|Microsoft Windows 10 Enterprise| +|PF32|C:\Program Files (x86)| +|NETVersion|4.8.4084.0| +|PSVersion|5.1.19041.610| +|Role|Standalone Workstation| +|IsDomainJoined|FALSE| + +### $Install +The `$Install` has a number of sub-hashtables, noteable of which is the MethodData table covered previously that can be viewed with the `DebugGetMethods` function. + +Other important values include the `ChosenMethod` hashtable, this table holds the 'current' MethodData table that is being processed in the `InstallAgent` function as it loops through the different methods; useful when debugging inside the `InstallAgent` function and you need to determine what method is currently being attempted. + +$Install.RequiredAction is useful to interrogate to determine what the final action type was determined by the script prior to install being called. + +Finally the `$Install.ChosenAgent.InstallPath` (below) along with the `$Install.AgentString` are called in the: + +```$Proc = [System.Diagnostics.Process]::Start($INST)``` + +Line to actually run setup with the given parameters for that Install type. + +|Name|Example Values| +| :- | :- | +|Results|{SelectedNETKit, SelectedAgentKit}| +|MethodResults|{E, D, J, F...}| +|ChosenNET|{Path, Version, FileName, InstallPath}| +|Sources|{ChosenNET, Demand, ChosenAgent, Network}| +|NETLOGONAccess|FALSE| +|ChosenAgent|{Path, Version, FileName, InstallPath}| +|MethodData|{E, D, G, F...}| +|RequiredAction|Upgrade Existing| +|NCServerAccess|TRUE| +|ChosenMethod|{Parameter, FailedAttempts, Type, Method...}| +|AgentString|/S /V" /qn AGENTACTIVATIONKEY=c2hvcnRlbmVkIGZvciB0aGlzIGRvY3VtZW50"| + +#### $Agent.ChosenAgent.InstallPath +|Name|Example Values| +| :- | :- | +|Path|\\\mydomain.local\netlogon\Agent\CurrentAgent\WindowsAgentSetup.exe| +|Version|2020.1.50425.0| +|FileName|WindowsAgentSetup.exe| +|InstallPath|C:\Windows\Temp\AGPO\Fetch\WindowsAgentSetup.exe| + +### $Agent +The `$Agent` table is provides an abundance of information about the Agent, it's current state or lack thereof, current processes etc. +* Appliance: Contains information merged from the ApplianceConfig.xml and ServerConfig.xml +* Path: Provides paths to configuration files and registry locations +* Processes: Provides information on running agent processes, needed when terminating them prior to upgrades etc. +* History: The values inside the History file, note that the History file is an XML output of the `$Agent.Appliance` when the agent is successfully installed/upgraded +* Services: Contains information on the Windows Services for the Agent/Maintenance +* Registry: Contains all the information from the registry uninstall key for the Agent +* DecodedActivationKey: Constructed during the Agent Diagnosis processes, what the Activation Key looks like prior to being encoded to base64. +* Docs: linked to the `[xml]` and registry key objects retrieved in the `DiagnoseAgent` function, these are the raw items before they are parsed, validated and populated into the above hashtables + + +|Name|Example Values| +| :- | :- | +|Appliance|{ID, WindowsVersion, Version, SiteID...}| +|Path|{Registry, ServerConfigBackup, Checker, ApplianceConfig...}| +|Processes|{Windows Agent Service, Windows Agent Maintenance Service}| +|History|{ID, WindowsVersion, SiteID, AssignedServer...}| +|HealthOptional|{}| +|Services|{Failure, Data}| +|Health|{ProcessesExist, AgentStatus, Installed, ApplianceIDValid...}| +|Registry|{UninstallString, InstallLocation, DisplayVersion, InstallDate}| +|DecodedActivationKey|HTTPS://nc.mymsp.com:443\|1234567890\|1\|f01ebde4-9cd2-46a1-9e5b-a41961c9c43b\|0\| +|Docs|{Checker.log, Registry, ApplianceConfig.xml, AgentHistory.xml...}| + +#### $Agent.Appliance + +|Key|Example Values| +| :- | :- | +|ID|1424021110| +|WindowsVersion|2020.1.1202.0| +|Version|2020.1.0.202| +|SiteID|364| +|AssignedServer|ncentral.mymsp.com| +|LastInstall|16/02/2021 22:20| +|ActivationKey|c2hvcnRlbmVkIGZvciB0aGlzIGRvY3VtZW50| + +#### $Agent.Path + +|Key|Example Values| +| :- | :- | +|Registry|HKLM:\Software\...\Uninstall\{3B3CE3D0-96E7-498F-8BFD-3D5511C07012}| +|ServerConfigBackup|C:\Program Files (x86)\N-Able Technologies\Windows Agent\config\ServerConfig.xml.backup| +|Checker|C:\Program Files (x86)\N-Able Technologies\Windows Agent\bin\Checker.log| +|ApplianceConfig|C:\Program Files (x86)\N-Able Technologies\Windows Agent\config\ApplianceConfig.xml| +|ApplianceConfigBackup|C:\Program Files (x86)\N-Able Technologies\Windows Agent\config\ApplianceConfig.xml.backup| +|ServerConfig|C:\Program Files (x86)\N-Able Technologies\Windows Agent\config\ServerConfig.xml| +|History|C:\Windows\SecurityObscurity\Agent\AgentHistory.xml| + +#### $Agent.Health + +|Key|Example Values| +| :- | :- | +|ProcessesExist|TRUE| +|AgentStatus|Disabled| +|Installed|TRUE| +|ApplianceIDValid|TRUE| +|ServicesRunning|FALSE| +|ServicesBehaviorCorrect|FALSE| +|ServicesExist|TRUE| +|AssignedToPartnerServer|TRUE| +|ProcessesRunning|FALSE| +|VersionCorrect|FALSE| +|ServicesStartupCorrect|FALSE| \ No newline at end of file diff --git a/On-Demand Setup Package/{86753098-6753-0986-7530-986753098675}.zip b/On-Demand Setup Package/{86753098-6753-0986-7530-986753098675}.zip deleted file mode 100644 index f4e3f4d..0000000 Binary files a/On-Demand Setup Package/{86753098-6753-0986-7530-986753098675}.zip and /dev/null differ diff --git a/README.md b/README.md index cb017b6..e31f077 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,113 @@ # InstallAgent Automation Suite -This is a community-based Automation Suite intended as a replacement for the stock N-central Group Policy Installer Script as provided by N-able. It is not supported by SolarWinds MSP or N-able, so please do not contact their support department regarding any problems or questions about this script. In addition, please do not contact the support departments of any individual Partners in the SolarWinds MSP Community regarding the Automation Suite or its components. - -If you discover a problem with any component of the Automation Suite or have ideas on how it could be improved, post an issue on GitHub [](https://github.com/N-able/AgentDeploymentPackage/issues). Alternatively, post on the NRC discussion forum. +## Table of Contents +- [InstallAgent Automation Suite](#installagent-automation-suite) + * [Introduction](#introduction) + * [Status of Suite](#status-of-suite) + * [Release notes for 6.0.2](#release-notes-for-602) + * [Release notes for 6.0.1](#release-notes-for-601) + * [New and improved Features in AgentDeploymentPackage 6.0.0](#new-and-improved-features-in-agentdeploymentpackage-600) + * [Key Features](#key-features) +- [The "Registration Token" - Agent Deployment and Considerations](#the--registration-token----agent-deployment-and-considerations) + * [History](#history) + * [Overview](#overview) + * [Automation of token deployment](#automation-of-token-deployment) + * [Security](#security) +- [Components](#components) + * [Deployment Package](#deployment-package) + * [AMP-Based Custom Service Package](#amp-based-custom-service-package) + * [Custom PowerShell modules](#custom-powershell-modules) +- [Registration Token and installation preference](#registration-token-and-installation-preference) +- [Preparation](#preparation) + * [1 - Updating the Partner Configuration](#1---updating-the-partner-configuration) + * [2 - Configure N-Central for Automatic Device Import](#2---configure-n-central-for-automatic-device-import) + * [3 - Setup a Deployment Package](#3---setup-a-deployment-package) +- [Deployment](#deployment) + * [1 - Choose a Deployment Method](#1---choose-a-deployment-method) + + [On-Demand Deployment](#on-demand-deployment) + + [Group Policy Deployment](#group-policy-deployment) + * [OPTIONAL](#optional) + * [1a - Setup the N-Central Custom Service (Version 6.xx)](#1a---setup-the-n-central-custom-service--version-6xx-) + * [1b - Setup AMP based PartnerConfiguration update amps](#1b---setup-amp-based-partnerconfiguration-update-amps) + * [1c - Setup AMP based PartnerConfiguration update amps](#1c---setup-amp-based-partnerconfiguration-update-amps) + * [1d - Setup AMP based monitoring of the PartnerConfig.xml file](#1d---setup-amp-based-monitoring-of-the-partnerconfigxml-file) + + [Update PartnerConfig from CP](#update-partnerconfig-from-cp) + + [Update PartnerConfig from JWT](#update-partnerconfig-from-jwt) + * [2 - Review Deployment Package Results](#2---review-deployment-package-results) + + [Windows Event Log](#windows-event-log) + + [Windows Registry](#windows-registry) + - [InstallAgent Key](#installagent-key) + - [Diagnosis Key](#diagnosis-key) + - [Installation Key](#installation-key) + - [Repair Key](#repair-key) + + [N-Central Custom Service](#n-central-custom-service) +- [Testing and Troubleshooting](#testing-and-troubleshooting) + + [Agent Setup Launcher Exit Codes](#agent-setup-launcher-exit-codes) + + [Agent Setup Script Exit Codes](#agent-setup-script-exit-codes) +- [Excluding Devices](#excluding-devices) +- [Routine Updates](#routine-updates) +- [Credits](#credits) + +## Introduction +This is a community-based Automation Suite intended as a replacement for the stock N-central Group Policy Installer Script as provided by N-able. It is not supported by N-able, so please do not contact their support department regarding any problems or questions about this script. In addition, please do not contact the support departments of any individual Partners in the Community regarding the Automation Suite or its components. + +This suite is a fork of [Ryan Crowther Jr's AgentDeploymentPackage on GitHub](https://github.com/N-able/AgentDeploymentPackage/) and will soon be the future branch going forward as they have moved on to other projects. + +If you discover a problem with any component of the Automation Suite or have ideas on how it could be improved, [post an issue on GitHub](https://github.com/AngryProgrammerInside/InstallAgent/issues). Alternatively, post on the N-Central Slack Community chat. These tools are provided as-is, in the best of faith, by those Partners and Community Members involved in their development. If you use this in your environment, we would love to hear from you on GitHub! -# Key Features +## Status of Suite +All scripts in the suite have been run and tested as functional. It is currently considered in a 'beta' phase while wider testing is being performed. + +Feel free to provide feedback and lodge issues and they will be reviewed. + +## Release notes for 6.0.2 +* Fixed several small bugs, full [Release Notes](ReleaseNotes.md) +* Added WSDL endpoint based N-Central Server verification for environments where Echo/ICMP is blocked, or where additional verification that the server N-Central is up and accessible. Enabled by setting the `` attribute to **True** +* Added forced removal/cleanup when bad MSI uninstall information or MSI unable to remove old/rogue agent when needed using the AgentCleanup4.exe. Enabled by setting the `` attribute to **True** + +## Release notes for 6.0.1 +* Fixed a large number of bugs, full [Release Notes](ReleaseNotes.md) +* Added the ability enable/disable service policy enforcement with `` attribute for service timeouts/restarts. By default is now **False** as the service policy can get overriden by the maintenance service eventually. + +## New and improved Features in AgentDeploymentPackage 6.0.0 + +![](media/readme-image8.png) +* Registration token install method: + * Activation Key methods for upgrades + * Registration Key methods for new installs/repairs +* Sources for the registration token can include: + * Script input parameters + * A configuration file located in the root of the script folder + * Kelvin Tegelaar's AzNableProxy via an Azure Cloud function also on GitHub under [KelvinTegelaar/AzNableProxy](https://github.com/KelvinTegelaar/AzNableProxy) + * Last successful install configuration saved to a local file +* Functioning N-Central AMP scripts that support 2 methods for updating the configuration file used for installation + * Direct update of Customer ID/Registration Token and other values from N-Central Custom Property (CP) injected via N-Central API See: [How to N-Central API Automation](https://github.com/AngryProgrammerInside/NC-API-Documentation) for examples + * Automatic update of Customer ID/Registration token from values pulled from local Agent/Maintenance XML along with provided JWT (see above documentation) +* Functioning N-Central AMP script to update/renew expired/expiring tokens +* Legacy Support: If you still have old values within your GPO, you can use a flag within the LaunchInstaller.bat to ignore provided parameters and rely upon the configuration file +* Custom installation method data + * Through additional modules you can use your own source for CustomerID/Registration Token enumeration + * A sample module is provided +* Added a new LaunchInstaller.ps1 while still providing LaunchInstaller.bat, either can be used but those wanting to move away from batch files can. +* Optional upload of installation telemetry to Azure Cloud, giving insight into success/failure to help track checkins against N-Central + * Example modules provided +* Quality of Life for development and debugging: + * Added debugmode to the InstallAgent.ps1 to avoid self destruct and reload of modules + * Added debug function to provide Gridviews of common tables + * For more details on development debugging of this script, check out this page on GitHub + +## Key Features The **InstallAgent Automation Suite** provides the following key features for deployment and facilitation of the N-Central Agent: -* Automatic Installation of up to 2 distinct versions of the N-Central Agent per Domain environment, including: - * N-Central System-Level Agent 11.0.0.1114 (the latest Agent version that does not require .NET Framework 4.5.2, and therefore still compatible with Windows XP / Server 2003) - +* Automatic Installation of up to 2 distinct versions of the N-Central Agent * Automatic Installation of prerequisite software required by the **Agent Setup Script** and the N-Central Agent which, at time of publication, includes: * N-Central Agent Requirements * .NET Framework 4.5.2 for Current Agents (11.0.1.xxxx and Above) - * .NET Framework 4.0 for Legacy Agents (11.0.0.xxxx and Below) * Script Requirements - * PowerShell 2.0 for XP / Server 2003 / Vista / Server 2008 (requires manual reboot after Installation) - * .NET Framework 2.0 SP1 for XP / Server 2003 - * Service Packs for XP / Server 2003 / Vista / Server 2008 (requires manual reboot after Installation) + * PowerShell 2.0 for Windows 7 / Server 2008 R2 and up * Boot-Time or On-Demand Detection and Automatic Repair of degraded or non-functional Agents with the following symptoms: * Stopped Agent Service(s)/Process(es) @@ -37,7 +124,73 @@ The **InstallAgent Automation Suite** provides the following key features for de * Live Script Status Updates and Timestamps for last actions the Script has taken via Registry -* Highly Customizable for your environment - Configure different values for Legacy vs Current Agent installations, or for each Domain you deploy to +# The "Registration Token" - Agent Deployment and Considerations + +## History +An information disclosure vulnerability was found in the N-Central platform in certain circumstances, this lead to auto-import being disabled in 12.1 SP1 HF2. As a part of mitigating this vulnerability N-Able introduced registration tokens in 2020.1 to allow automatic import of devices. The patch notes regarding the tokens was as follows: + +>In order to enhance the security of the agent registration process, and to turn back on the ability to fully autoimport any discovered device, N-central now requires that a new Registration Token be provided during any +agent install. +> +> We've taken care to make this new security requirement as easy as possible for your technicians – the customerspecific installers now come pre-bundled with a registration token, and you can also specify the token as a +command line parameter for Group Policy or scripted deployments of the agent. +> +> From an administrative point of view, you're also going to find this to be a breeze – tokens are automatically +regenerated by N-central, and you can control how long the tokens last for via the **Administration > Defaults > +Agent/Probe Settings** page, on the new **Registration Tokens** tab – that's also where you can revoke tokens, +should the need arise. +> +>Please note that because of the new registration token feature, older versions of the agent installer will no +longer be able to register themselves with N-central. +Because these changes are in-place, we've also fully re-enabled automatic importation of devices. You're +welcome! + + +## Overview +The registration token is a simple random GUID that in combination with the customer Id acts as a password for installation. With 2^122 possible tokens values there is no practical method for brute forcing, ensuring that only those with tokens generated for a customer/site can install an agent. + +From a practical standpoint getting the token from your N-Central server to an endpoint poses several challenges + +* By default tokens will expire after a period of time +* ***At time of writing*** tokens are *only* generated by human interaction with the N-Central UI with one of: + * Under **Actions -> Download Agent/Probe** and click on either **Get Registration Token** or download a Customer/Site Specific Agent/Probe that has the token pre-baked in. + * Click on an device that supports agent installation, then go to **Settings -> Local Agent** and click **Get Activation Key** + +I note at time of writing as there is a planned feature to allow it to be refreshed via the N-Central API, otherwise when you first upgrade to a 202x.x platform you will need to manually generate them. + +These challenges make automation of registration key deployment difficult, with many MSPs simply disabling the token expiration, and some engineers spending their mornings clicking on the same button repeatedly and pasting tokens out to a spreadsheet to then manually update in GPOs. + +The other challenge is how to deal with deploying at scale to the following kinds of environments: +* Customers with multiple sites but the same domain +* Multiple customers/sites on a multi-tenanted domains + +## Automation of token deployment +To tackle the challenges of Agent deployment the community has produced several solutions that involve the N-Central API in some way. To utilise the N-Central API requires what is know as JSON Web Token (**JWT**). A JWT effectively allows anyone with that token to be able to access your N-Central server with all of the permissions associated with the user account it was generated from, so security consideration needs to be taken in where it is stored and how it is used. + +Here are some of the main methods of token retrieval that have been developed by the N-Able community: +* Direct passing of Customer, Token and JWT held in a GPO through to a script that pulls the token from N-Central API then installs +* Passing the Customer ID to an Agent install script that uses an authenticated Azure function with the JWT hidden in the Configuration +* Retrieving the token values from the N-Central API then re-injecting them to Custom Properties (CPs), then then injecting those values into installation configuration files. + +The updates to the InstallAgent are intended to take advantage of the PartnerConfig configuration file by containing the Customer ID and registration token needed for new or upgrade installations, as well as configuration of the URI and AuthCode needed for an Azure based proxy token if desired. + +To update the PartnerConfig file with these values two AMPs have been provided to routinely update the configuration file as needed: +* **Refresh Agent from JWT-API** - This method uses local enumeration of the agent it is run on to gather the Customer ID and Configuration needed, along with a JWT passed to the running agent. + +* **Refresh Agent Token from CP** - This method uses some local enumeration for the N-Central server address, but otherwise it is intended to pass through the Customer ID and token from Custom Properties of the Customer/Site + +* **RequestAzWebProxyToken() function** - Built into the script is a function that takes the Uri and Authcode and pulls the relevant registration token. The AzNableProxy is simple to deploy to your own company Azure subscription, with a few clicks and a coffee break this token retrieval method can be adapted to many scenarios with just a CustomerID provided. + +## Security +Security is at the forefront of everyone's mind when looking at automated deployment, MSPs are continually targetted by bad faith actors, given this some further detail on security of tokens and JWT will be explored for each method here: + +* **Refresh Agent from JWT-API** - This method passes the JWT through the local agent securely over HTTPS before being passed into the AMP parameters. The JWT is never written to the disk but is resident in the Agent process and AMP for several seconds. An attacker would need a compromised device, which for GPO deployments is a Domain Controller, with escalated priveleges to have a chance at obtaining the JWT for the few random seconds it is in memory; given this the method is considered to have very low risk of exposure. + +* **Refresh Agent Token from CP** - In this method you would likely populate Custom Properties with a custom script from a device that is located inside the perimeter network that is secured with 2FA. Treat the JWT as you would any username/password with the principal of least privelage. + +* **RequestAzWebProxyToken() function** - This method's JWT is secured inside of a function configuration file `local.settings.json` in an Azure subscription, accessed by accounts that should be secured by 2FA. Microsoft's Azure functions are secure by design, and their security meets many international standards. If you have compliance requirements, store the token in a [Key Vault](https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references). + +Another component of the automation suite to consider in terms of security is the Refresh Agent Token.amp automation. This AMP directly takes a username and password of an account where MFA must be disabled, as well as a JWT of an account that can retrieve tokens. While these will be passed as variables and should only exist in memory while it executes, it is recommended to only run this automation item on a trusted device inside your perimeter network. # Components @@ -50,24 +203,23 @@ The **Deployment Package** is suitable by itself for all deployments, and contai * **AGENT** Folder * **CurrentAgent** Folder * 1**NET4_5_2-Universal.exe** - .NET Framework Installer required by Current Agents (11.0.1.xxxx and Above) - * **LegacyAgent** Folder - * 1**NET4_0-Universal.exe** - .NET Framework Installer required by Legacy Agents (11.0.0.xxxx and Below) - * **WindowsAgentSetup.exe** - System-Level Agent Installer 11.0.0.1114 (latest Agent Installer compatible with Windows XP / Server 2003) * **Lib** Folder * **InstallAgent-Core.psm1** - Core Functions file for the **Agent Setup Script** (InstallAgent.ps1) which contains most of its key operations - * **PS2Install** Folder - * **XPTools** Folder - Windows Support Tools for Windows XP / Server 2003 (Allows for Command-Line File Download) - * **2003** Folder - * support.cab - * suptools.msi - * **XP** Folder - * sup_pro.cab - * sup_srv.cab - * support.cab - * suptools.msi * **InstallAgent.ps1** - **Agent Setup Script,** the main/wrapper script, which contains most pre-defined constants and structures for execution * **LaunchInstaller.bat** - **Agent Setup Launcher,** the launcher script, which is called On-Demand (click-to-run) or by Group Policy (calling the Launcher by either method **requires an N-Central Customer/Site ID number as a Parameter for any new Agent installations**) * **PartnerConfig.xml** - **Partner Configuration,** which is used to dictate most variable options to the **Agent Setup Script** +* **AMPs** folder + * **Refresh Agent Token.amp** - AMP wrapper for below token refresh/update PS1 file + * **Refresh Agent Token.ps1** - Core code for refreshing/updating tokens inside of N-Central without human interaction + * **Update PartnerConfig from JWT-API.amp** - **AMP wrapper**, for below ps1 file that uses local agent information and a JWT token to automatically populate a PartnerConfig.xml + * **Update PartnerConfig from JWT-API.ps1** - **PS Code** for populating PartnerConfig + * **Update PartnerConfig from CP.amp** - **AMP wrapper**, for below ps1 file that populates the PartnerConfig from your N-Central Customer Property + * **Update PartnerConfig from CP.ps1** - **PS Code** for populating PartnerConfig from a Custom Organization Property +* **Custom Library** folder + * **CustomOverrideExample.psm1** - Example function overrides for extended Azure telemetry capability + * **GetCustomInstallMethodExamples.psm1** - Example function to override or change your install method data. This function is called by default at the approriate time just prior to validation checks + * **LibReadme.md** - Brief instruction on usage of above files + 1 Download Instructions for these items are included at the designated location in order to reduce overall package size, as they are already freely available on the web @@ -75,24 +227,74 @@ The **Deployment Package** is suitable by itself for all deployments, and contai The **Custom Service Package** is an optional download available with the **Deployment Package,** which integrates with N-Central to collect and monitor information about the most recent run of the **Deployment Package.** -***ATTENTION - The Custom Service Package is no longer compatible with Deployment Package 5.0.0 and above*** - The **Custom Service Package** contains the following items: -* **CustomService** Folder - Not required for any other Packages - * **InstallAgent status.amp** - Automation Policy run by the Custom Service - * **InstallAgent status - Tim Wiser.amp** - Automation Policy run by the Custom Service, for **Deployment Packages** still using Tim's Registry key (4.01 and Below) - * **Agent Installer Script Status.xml** - Custom Service Configuration file +* **Custom Service Package** Folder - Not required for any other Packages + * **Agent Installer v6.amp** - Automation Policy run by the Custom Service + * **Custom Service.ps1** - Core logic of the above AMP file + * **Agent Installer.xml** - The custom service file that you can import into N-Central + + +## Custom PowerShell modules +The default InstallAgent-Core.psm1 module provides the default behaviors, and now includes AzNableProxy token lookup by default. + +By using custom modules that override default funtions you can achieve most any features you need for edge case deployments. +Add your custom modules to the **Agent\Lib** folder and update the $SC.Names.LibraryFiles array. + +Examples of what can be achieved is illustrated with the following files within the Custom Library folder: +* **GetCustomInstallMethodExamples.psm1** - Demonstrates how you can provide a custom Install Method information back to the correct Hashtable +* **CustomOverrideExample.psm1** - Demonstrates how you can + * Add additional telemetry when calling the AzNableProxy GET function with a few extra lines of code + * Deliver detailed telemetry on Exit or Failure with a few extra lines of code + +# Registration Token and installation preference +Prior to the advent of the registration token you simply supplied the Customer ID and domain to the script parameters in the GPO and everything else would work. + +After the introduction of the requirements of the registration token, the challenge has become *how* to get the registration token to the device for both activation key and registration key type installation. + +The methods that have been implemented by the community include: +* Custom Script that takes the Customer ID and Token but requiring update of the token at the needed refresh date +* Variations on token lookup via N-Central API with a JWT such as [DeployTheNCAgent GitHub community script by Chris Reid](https://github.com/N-able/ScriptsAndAutomationPolicies/blob/master/DeployTheNCAgent/DeployTheNCAgent.ps1) +* Token lookup by the more secure [AzNableProxy method by Kelvin Tegelaar](https://github.com/KelvinTegelaar/AzNableProxy) that hides the JWT +* Update of the Customer ID/Token in PartnerConfig via N-Central AMP + +The updates to the deployment package uses all the above methods in some manner to a achieve high a rate of installation success. Here is the list of installation methods available by default for upgrading or installing new agents in order: + +1. Activation Key : Token (Partner Config) / Appliance ID (Existing Installation) +2. Activation Key : Token (Current Script) / Appliance ID (Existing Installation) +3. Activation Key : Token / Appliance ID (Historical Installation) +4. Activation Key : AzNableProxy Token / Customer ID, Appliance ID (Existing Installation) +5. Activation Key : Customer ID (Current Script) / AzNableProxy Token / Appliance ID (Existing Installation) +6. Site ID/Registration Token (Current Script) +7. Customer ID / Registration Token (Partner Config) +8. Customer ID / AzNableProxy Token (Partner Config) +9. Customer ID / AzNableProxy Token (Current Script) + +If data from a source is not available the script will not attempt to install with that method data. In general the preference is PartnerConfig -> Script Parameters -> AzNableProxy. Let's go through some examples: + +Scenario 1: You deploy the package to your Netlogon directory, and disable the flag in the LaunchInstaller.bat to read in script parameters. AzNableProxy elements are not in the PartnerConfig. +* Upgrade path: It will attempt method 1 first, then fall back to 3. Failing that it will attempt method 7. +* New install: It will attempt method 7 with the provided CustomerID/Token, no fallback. + +Scenario 2: The package is deployed, same scenario as above but you have AzNableProxy deployed and the XML elements in the PartnerConfig updated. +* Upgrade path: It will attempt method 1, 3, 4, 5 then 7, 8 +* New Install: It will attempt method 7 and 8. + +Scenario 3: The package is deployed as above. LaunchInstaller is set to read parameters. Customer ID and Registration token is provided as parameters +* Upgrade Path: 1 -> 9 +* New Install: 6 -> 9 # Preparation -## 1 - Update the Partner Configuration +## 1 - Updating the Partner Configuration **The developers recommend editing the Partner Configuration (PartnerConfig.xml) with Visual Studio Code, however feel free to use your favorite text editor that supports "UTF-8 w/ BOM" Encoding.** Open **PartnerConfig.xml** to begin. This **Partner Configuration** is your one-stop shop for configuring the **Deployment Package.** Enter data for each value between the relevant XML Tags (many values are already assigned Defaults to serve as an example). Information about the Expected Format and Purpose of each Configuration Value is contained above each value itself. Consider the table below a desk reference. +In normal circumstances you would configure values like Service Behavior and Script Behavior, then the rest will be updated/injected via the provided AMPs. + | Category | Value Name | Default Value | Mandatory | Purpose | | -------- | ---------- | ------------- | --------- | ------- | | **Branding** | *ErrorContactInfo* | | No | This is a space for your business contact info (Company Name, Address, Support Phone Number, etc.). This will be printed at the bottom of the Event Log entry when a documented error occurs. If using multiple lines, start your information at the **beginning of each line** (no indent) in the tag space. | @@ -101,15 +303,15 @@ Open **PartnerConfig.xml** to begin. This **Partner Configuration** is your one- | **ServiceBehavior** | *ActionA* | RESTART | No | Windows Service Failure Actions for the Agent Services - What Windows will do when the Service fails | | | *ActionB* | RESTART | No | You must specify actions consecutively, for example, if *ActionC* is specified, then neither *ActionA* nor *ActionB* can be empty. | | | *ActionC* | RESTART | No | | -| | *DelayA* | RESTART | No | Windows Service Failure Delays (in seconds) for the Agent Services - How long Windows will wait before taking each Action | -| | *DelayB* | RESTART | No | You must specify delays consecutively, for example, if *DelayC* is specified, then neither *DelayA* nor *DelayB* can be empty. | -| | *DelayC* | RESTART | No | Each Action should have a corresponding Delay, but if an Action is left blank, **the corresponding Delay value will be ignored.** | +| | *DelayA* | 120 | No | Windows Service Failure Delays (in seconds) for the Agent Services - How long Windows will wait before taking each Action - Between 0 and 3600 seconds | +| | *DelayB* | 120 | No | You must specify delays consecutively, for example, if *DelayC* is specified, then neither *DelayA* nor *DelayB* can be empty. | +| | *DelayC* | 120 | No | Each Action should have a corresponding Delay, but if an Action is left blank, **the corresponding Delay value will be ignored.** | | | *Command* | | No | Command to execute for each **RUN** Action specified. **Use the absolute path to the Command to ensure your Command will run when and how you expect.** | | | *Reset* | 1440 | No | Windows Service Failure Reset Period (in minutes) for the Agent Services - How long until Windows re-attempts the Failure Actions | | | *Startup* | Auto | Yes | Desired Startup Type for the Agent Services | | **Server** | *NCServerAddress* | | Yes | Your N-Central Server Address, which can be found in the **Server Address** box on the **Administration > Defaults > Appliance Settings > Communication Settings** page. **Do not copy and paste a Web Address.** For example, if you login to **https://n-central.mymsp.com/MyServiceOrganization/**, the Server Address may be **n-central.mymsp.com** instead. **You will always get the correct value from N-Central itself.** | | | *PingTolerance* | 20 | Yes | Percentage value of dropped packets allowed when testing connectivity to the *NCServerAddress* specified | -| | *PingCount* | 10 | Yes | The number of pings performed when testing connectivity to the *NCServerAddress* specified | +| | *PingCount* | 20 | Yes | The number of pings performed when testing connectivity to the *NCServerAddress* specified | | | *ProxyString* | | No | Proxy String if the Agent requires one to reach the *NCServerAddress* specified - see the **Partner Configuration** file for Acceptable Formats | | **Deployment** | *LocalFolder* | C:\AGENT | Yes | This is the path of a Folder left behind on the system for retaining Local Activation Info for the Agent from previous runs of the **Deployment Package.** It contains no more information than is already in the Agent installation folder, but is there **in case the Agent is removed by a user, technician or N-Central Update and not installed again afterward.** | | | *NetworkFolder* | AGENT | Yes | Name of the Root Folder that contains the **Deployment Package.** In Domain Group Policy Deployments, this must be placed in the NETLOGON Folder. **NOTE - Partners CURRENTLY using Versions of the InstallAgent Deployment Package PRIOR to 5.0.0 should NOT change this value from its Default (AGENT) unless they wish to update all existing GPOs with the new location** | @@ -118,15 +320,12 @@ Open **PartnerConfig.xml** to begin. This **Partner Configuration** is your one- | | *NETVersion* | 4.5.2 | Yes | User-Friendly Version of .NET Framework to install on Typical systems - for verification/logging purposes | | | *NETFileVersion* | 4.5.51209.34209 | Yes | Windows Version of the .NET Framework Installer to use on Typical systems, which should match the **File Version** property when you right-click **Properties > Details.** | | | *SOAgentFileName* | WindowsAgentSetup.exe | Yes | Name of the Agent Installer supplied in the Typical *InstallFolder* - change this each time you upgrade your N-Central Server, and **be sure to use the System-Level Agent Installer** | -| | *SOAgentVersion* | 12.0.1.118 | Yes | User-Friendly (N-Central) Version of the Agent to install on Typical systems - for verification/logging purposes | -| | *SOAgentFileVersion* | 12.0.10118.0 | Yes | Windows Version of the Agent Installer to use on Typical systems, which should match the **File Version** property when you right-click **Properties > Details.** | -| **Deployment (Legacy)** | *InstallFolder* | LegacyAgent | Yes | Name of the Folder used by the Package to hold relevant Installers for Legacy Agents (11.0.0.xxxx and Below) | -| | *NETFileName* | NET4_0-Universal.exe | Yes | Name of the .NET Framework Installer supplied in the Legacy *InstallFolder* - The supplied Installer is the only version suitable for Windows XP / Server 2003 and the N-Central Agent (since .NET 4.0 is a minimum requirement of the Agent, and maximum version for XP / 2003) | -| | *NETVersion* | 4.0 | Yes | User-Friendly Version of .NET Framework to install on Legacy systems - for verification/logging purposes | -| | *NETFileVersion* | 4.0.30319.1 | Yes | Windows Version of the .NET Framework Installer to use on Typical systems, which should match the **File Version** property when you right-click **Properties > Details.** | -| | *SOAgentFileName* | WindowsAgentSetup.exe | Yes | Name of the Agent Installer supplied in the Typical *InstallFolder* - The supplied Installer is the latest version suitable for Windows XP / Server 2003 - only change this if you elect to use an earlier version of the Installer | -| | *SOAgentVersion* | 11.0.0.1114 | Yes | User-Friendly (N-Central) Version of the Agent to install on Typical systems - for verification/logging purposes | -| | *SOAgentFileVersion* | 11.0.2114.0 | Yes | Windows Version of the Agent Installer to use on Typical systems, which should match the **File Version** property when you right-click **Properties > Details.** | +| | *SOAgentVersion* | 2020.1.5.425 | Yes | User-Friendly (N-Central) Version of the Agent to install on Typical systems - for verification/logging purposes | +| | *SOAgentFileVersion* | 2020.1.50425.0 | Yes | Windows Version of the Agent Installer to use on Typical systems, which should match the **File Version** property when you right-click **Properties > Details.** | +| | *CustomerId* | null | No | The Customer ID value as found under **Administration > Customers**, typically populated via a provided AMP| +| | *RegistrationToken* | null | No | The Registration Token value as found under **Actions > Download Agent/Probe > Get Registration Token**, typically populated via a provided AMP| +| | *AzNableProxyUri* | null | No | The URI of the AzNableProxy function as found under the function app overview but without the leading https:// , ie. mytokenproxy.azurewebsite.net. Typically updated via a provided AMP.| +| | *AzNableAuthCode* | null | No | The AuthCode of the AzNableProxy function as found under the **Functions > GET function > Function Keys**. Typically updated via a provided AMP.| Once you've made your adjustments, in most cases, you should be able to utilize the Configuration Values for your all your clients, but you may elect to customize them for specific environments. For example, you may wish to increase the *PingTolerance* for a Customer/Site where you know there is significant latency, and therefore Agents may have trouble reliably communicating with the N-Central Server. @@ -135,7 +334,7 @@ A small amount of customization can also be made in the **Agent Launcher Script* | Value Name | Default Value | Mandatory | Purpose | | ---------- | ------------- | --------- | ------- | | *TempFolder* | C:\Windows\Temp\AGPO | Yes | This should be a Local Folder on the system where the **Deployment Package** components work from, **NOT to be confused with the *LocalFolder* value in the Partner Configuration.** Whether run by Group Policy or On-Demand (click-to-run), the required components are dropped here during execution and then **removed upon termination of the Package.** | -| *DLThreshold* | 3 | Yes | This is the maximum number of attempts the **Agent Setup Launcher** will make to download any prerequisite components needed for Agent installation. If this value is exceeded, the **Launcher will terminate, and it must be run again.** | +| *NoArgs* | 0 | Yes | Disable the usage of arguments passed to this script. This way you can **not** pass CustomerID and Token through arguments, but if you have an older GPO that still uses a domainname or the term AUTO, you can set this value to 1. This way you don't need to change all those GPO's. **If set to 1, you do need another way to pass CustomerID and Token, or through the PartnerConfig.xml, or through azNableProxy** | If you elect to make any changes, you will need to adjust the values **immediately following the equals (=) sign** in their relevant **SET** statements. For example: @@ -175,8 +374,6 @@ In N-Central, Devices are **not automatically imported into the All Devices View 4 - Add the System-Level Agent Installer to the ***NetworkFolder\\(Typical)InstallFolder*** location -5 - If you choose to use a different Legacy Agent, perform steps 4 and 5 for the *(Legacy)* variety of each value in the **Partner Configuration,** and replace the existing Agent Installer in the ***NetworkFolder\\(Legacy)InstallFolder*** location - The **Deployment Package** is now ready for On-Demand or Group Policy deployments! # Deployment @@ -195,7 +392,7 @@ This method is suitable for one-off Devices, or those that do not belong to a Wi Example - **DO NOT USE** -**F:\DeploymentStuff\AGENT\LaunchInstaller.bat 170** +**F:\DeploymentStuff\AGENT\LaunchInstaller.bat 170 90d3f270-c8ac-48dc-9466-0d48f2ffc339** Another use case for On-Demand Deployment may be running the **Deployment Package** for **Repair or Re-Installation purposes.** @@ -216,7 +413,7 @@ This is by far, the preferred method for enterprise, or Windows Domain-managed e **NOTE - Partners CURRENTLY using Versions of the InstallAgent Deployment Package PRIOR to 5.0.0** * Simply replace the **AGENT** Folder in its entirety with the *NetworkFolder* you've defined in the **Partner Configuration** -* You will NOT need to revise your existing GPO Parameters at all in the following steps, **UNLESS you have elected to change the *NetworkFolder* from its Default Value (AGENT)** +* You will NOT need to revise your existing GPO Parameters at all in the following steps, **UNLESS you have elected to change the *NetworkFolder* from its Default Value (AGENT)**. 2 - In the **Group Policy Management Console,** create a new **Group Policy Object** and link to a suitable OU or to the Domain itself. You might name it **InstallAgent Deployment Package,** **Agent Deployment** or similar. @@ -224,27 +421,106 @@ This is by far, the preferred method for enterprise, or Windows Domain-managed e 4 - **Right-click and Edit** the new GPO node, then expand the following nodes in the left-hand pane: **Computer Configuration > Policies > Windows Settings > Scripts.** Open the **Startup** item on the right. -5 - Click the **Add** button, and for the Script Name box, **Browse** to **\\clientdomain.name\NETLOGON\**NetworkFolder*** and select **LaunchInstaller.bat.** +5 - Click the **Add** button, and for the Script Name box, **Browse** to **\\\clientdomain.name\NETLOGON\**NetworkFolder*** and select **LaunchInstaller.bat.** 6 - In N-Central, locate the Customer/Site ID for the client's Domain by reviewing either the **Administration > Customers** page at the Service Organization Level, or the **Administration > Sites** page at the Customer Level. Consider your environment before selecting the ID. In most scenarios, **you will probably want the Customer-Level ID,** since most Customers have a single Domain, or you may simply not use the Site Level in your N-Central setup. If, however, you have a **Domain or OU that is specific to only one Site,** you may opt to use that specific Site ID to have new Devices import there directly, instead of at the Customer Level. -7 - In the **Script Parameters** box, type the 3-digit Customer/Site ID that you have selected for that client's Domain. Your entries should look similar to the screenshot below (client Domain name blocked out for privacy): +7a - For existing deployments of Time Wiser's VBS based InstallAgent you will have typically have the domain or 'auto' variable in the. In the 5.0.1 version of the DeploymentPackage you may only have the Customer ID -![](media/readme-image2.png) +Rather than edit the GPO in every single GPO deployment you have you can simply edit the NoArgs variable line in the LaunchInstaller.bat +```bat +REM - Don't use the arguments. This way, CustomerID and Registration Token aren't taken from the arguments. This to support older GPO's, that had CustomerID and Domainname as arguments +SET NoArgs=0 +``` +Change this flag to a value *other* than 0 and it will ignore all script parameters. Once you have your N-Central AMP in place to update the Customer ID/Registration token values. -Congratulations! Your **Group Policy Deployment** is ready for action! +7b - In the **Script Parameters** box, depending on how you intend to provide the registration token information to the device: +* Enter NO script parameters: The device will then retrieve details from the PartnerConfig or failback to the AzNableProxy depending on the context. +* Enter the Customer ID only: The device will use the Customer ID preferentially during certain install types +* Enter the Customer ID and Registration Token: The device will use the Customer ID and Registration token in highest preference when performing a new install -## OPTIONAL -## 1a - Setup the N-Central Custom Service (Version 4.xx) +![](media/readme-image2.png) -***ATTENTION - The Custom Service Package is no longer compatible with Deployment Package 5.0.0 and above*** +Congratulations! Your **Group Policy Deployment** is ready for action! -Another option for viewing results of the **Deployment Package** is to **Monitor the Registry updates it makes to the Device with N-Central.** You can setup the **Custom Service Package** to do exactly that! The current Version of the **Custom Service** is called **Agent Installer Script Status** in N-Central, and is compatible with the following: -* **InstallAgent Deployment Package 4.xx** -* **N-Central 9.5 SP1 and Above** -For setup and configuration help, consult the appropriate SolarWinds MSP N-Central Documentation for **Importing a Custom Service.** +## OPTIONAL +## 1a - Setup the N-Central Custom Service (Version 6.xx) + +* **InstallAgent Deployment Package 6.xx** +* **N-Able N-central 2020.1 HF5 and Above** +For setup and configuration help, consult the appropriate N-Able N-Central Documentation for **Importing a Custom Service.** + +## 1b - Setup AMP based PartnerConfiguration update amps +To manage Customer agent install tokens automatically without setting the expiration, deploy the AMP to your N-Central server then: +* Create a security role for the automation account, lease privelege requires: + * Devices -> Network Devices -> Edit Device Settings \[Read Only\] + * Devices -> Network Devices -> Registration Tokens \[Manage\] +* Create an automation account with no MFA and note the Username, Password and JWT +* Create a N-Central task against a server, preferably an internal server with the most direct line of sight to the N-Central server +* Fill out the input parameters for the AMP + +| Parameter | Value | +| ----- | ----- | +| Username | username of the automation account | +| Password | password of the automation account | +| N-Central FQDN | address of the N-Central server. eg. `n-central.mymsp.com` +| Expiration Tolerance | Days prior to the token expiration to force a update on the token (positive value) +| JWT | JWT of the automation account | + +## 1c - Setup AMP based PartnerConfiguration update amps +Of the two AMP based tools provided, deploy the ones that most suits your unique business/customer/security situation. As needed you can create and deploy your own custom module to override default Install Methods. To install these AMPS consult the appropriate N-Able N-Central Documentation for **Importing a Custom Service.** + +## 1d - Setup AMP based monitoring of the PartnerConfig.xml file +With Agent AD Status, you can check the health of the agent folder in your Active Directory. +This AMP is only meant to be applied to domain controllers and will check for the following: +* Is the PartnerConfig.xml file available +* Version mentioned in PartnerConfig.xml file +* Version of nCentral in PartnerConfig.xml file +* Is the agent installation file available on the correct place +* Are the version of the agent installation file and the one mentioned in the PartnerConfig.xml file the same +* Is there a CustomerID in the PartnerConfig.xml file (and what is it) +* Is there a Registration Token in the PartnerConfig.xml file (not displayed, just if present or not) +* Is the correct GPO deployed in AD (Name is configurable) + +### Update PartnerConfig from CP +Before deploying this AMP you will need to: +* Create your own Custom Properties for Customers/Properties +* Create a custom script that re-injects the CustomerID/Token into Customers/Properties (see [NC-API-Documentation](https://github.com/AngryProgrammerInside/NC-API-Documentation/) example) + +Once done point the AMP at your applicable domain controller(s) and populate the following fields that will be injected into the PartnerConfig.xml: +| Name | Default | Value | +| -----| ----- | ----- | +| Registration Token| *null* | Custom Property | +| Customer ID | *null* | Custom Property | +| Local Folder | *C:\Windows\SecurityThroughObscurity* | You can set the default to a unique location within the AMP, this is where the History.XML gets saves +| Network Folder | Agent | You can set this the name of your own Network folder, this is the name of the folder under Netlogon | +| SO Agent File Name | WindowsAgentSetup.exe | Name of the current Agent setup executable.| +| SO Agent Version | 2020.1.5.425 | Friendly version number | +| SO Agent File | 2020.1.50425.0 | Internal file version number | +| Branding | *My MSP @ MSP.com * | Informational detail display in Application log | +| AzNableProxuUri | *null* | Uri of your AzNableProxy | +| AzNableAuthCode | *null* | Auth key code for you GET function + +Once verified working as intended you can place this on a schedule if required. + +### Update PartnerConfig from JWT +This AMP only requires the JWT and will automatically discover the CustomerId and Token through the N-Central API. Create an PowerShell automation account for your account with the following role permissions for **Devices -> Network Devices --> Registration Tokens \[Manage\]** + +Once done point the AMP at your applicable domain controller(s) and populate the following fields: +| Name | Default | Value | +| -----| ----- | ----- | +| Local Folder | *C:\Windows\SecurityThroughObscurity* | You can set the default to a unique location within the AMP, this is where the History.XML gets saves +| Network Folder | Agent | You can set this the name of your own Network folder, this is the name of the folder under Netlogon | +| SO Agent File Name | WindowsAgentSetup.exe | Name of the current Agent setup executable.| +| SO Agent Version | 2020.1.5.425 | Friendly version number | +| SO Agent File | 2020.1.50425.0 | Internal file version number | +| Branding | *My MSP @ MSP . com* | Informational detail display in Application log | +| AzNableProxuUri | *null* | Uri of your AzNableProxy | +| AzNableAuthCode | *null* | Auth key code for you GET function + +Once verified working as intended you can place this on a schedule. ## 2 - Review Deployment Package Results @@ -267,8 +543,8 @@ To get a better idea of exactly how damaged this installation is, we can supplem Values are logged and regularly updated by the **Deployment Package** during execution, at the following keys and their children: -* **HKLM:\SOFTWARE\SolarWinds MSP Community\LaunchInstaller** - For values logged by the **Agent Setup Launcher** -* **HKLM:\SOFTWARE\SolarWinds MSP Community\InstallAgent** - For values logged by the **Agent Setup Script** +* **HKLM:\SOFTWARE\N-Able Community\LaunchInstaller** - For values logged by the **Agent Setup Launcher** +* **HKLM:\SOFTWARE\N-Able Community\InstallAgent** - For values logged by the **Agent Setup Script** Therefore, you can poll this data at any time and get a sense of what the **Deployment Package** is doing or has done **both during and after execution.** The next section details the Registry Keys and Values that appear here. @@ -348,22 +624,41 @@ Lastly in our example, the Agent Service Configuration has been compromised once ### N-Central Custom Service -***ATTENTION - The Custom Service Package is no longer compatible with Deployment Package 5.0.0 and above*** - If you have setup the **Custom Service Package,** you can also review the results of the most recent run of the **Deployment Package** directly in N-Central! -A sample of output from the **Custom Service** in N-Central can be seen below: +A sample of output from the **Custom Service** in N-Central can be seen below for a service in a healthy state: ![](media/readme-image0.png) -| Parameter | Thresholds | Description | -| --------- | ---------- | ----------- | -| *Date/Time of Last Execution* | None | Timestamp of most recent **Agent Setup Script** run | -| *Path to Installer Files* | None | Location of the *NetworkFolder* | -| *Script Version* | Warning on Outdated Version | Detected Version of the **Agent Setup Script** | -| *Last Successful Completion* | None | Timestamp of last Successful result (Code 10) | -| *Last Result Code* | Warning on Codes 1 thru 9, Failed on Code 0 | Exit Code returned by the most recent **Agent Setup Script** run | -| *Last Execution Mode* | Warning on Interactive Mode | Execution Mode used by the most recent **Agent Setup Script** run | +| Parameter |Type | Null value| Thresholds | Description | +| --------- | --------- | --------- | ---------- | ----------- | +| *Script Action*| String | - | **Not** Graceful Exit | The last action take by the Agent Installer | +| *Script Exit Code* | Number | 404 | **Not** 0 | Exit code of the Agent Installer script | +| *Script Version* | Number | 404 | Warning on Outdated Version | Detected Version of the **Agent Setup Script** | +| *Script Result* | String | - | None | The last action take by the Agent Installer | +| *Script Last Ran* | DateTime | 1/1/1900 | Default timedate threshold | Timestamp of last run | +| *Script Mode* | String | - | None | Displays if GPO or On-Demand | +| *Script Sequence* | String | - | The last point/function being called | Execution Mode used by the most recent **Agent Setup Script** run | +| *Agent Last Diagnosed* | DateTime | 1/1/1900 | Default timedate threshold | Displays when the Diagnosis function last ran, typically this is each run | +| *Agent Last Installed* | DateTime | 1/1/1900 | Default timedate threshold | Displays when the Agent called the Install function to deploy the package + +
+ +When deploying the custom service package to a service template, it is recommmended to disable threshold monitoring of *Agent Last Diagnosed* and *Agent Last Installed* as the values may not always be available. It is optional to have a threshold on the *Script Last Ran* depending how often you reboot and how often you expect machines to run the Agent Installer script. + +Some further examples of output you may encounter from the service: + +| **Old version of script warning**| +| -- | +|![Old Version of script](media/readme-image9.png)| + +| **Script has never run/registry values missing** | +| -- | +| ![No registry values](media/readme-image10.png) | + +| **Agent upgrade failed** | +| -- | +| ![Agent upgrade failed](media/readme-image11.png) | # Testing and Troubleshooting @@ -378,8 +673,7 @@ That said, due to the complexity of the **Deployment Package,** there are severa | --------- | -------- | ----------- | ---------- | | 10 | 10 | Successful Execution | None - All prerequisite Software is installed and the **Agent Setup Script** was launched successfully | | 11 | 11 | Execution Failed | General Failure - The Event Log will contain the details on the failure and resolution. | -| 12 | 12 | Reboot Required | The system requires a manual reboot for prerequisite Software installation. Run the Launcher again post-boot to continue setup. | -| 13 | 13 | OS Not Compatible | The Windows Operating System is not compatible with any Legacy or Current Agents (Windows 2000/ME and older). This **may** also occur on brand new Windows Releases, if Microsoft changes its build scheme again, as it did with Windows 10. | +| 13 | 13 | OS Not Compatible | The Windows Operating System is not compatible with any Legacy or Current Agents (Windows Viata/2008 and older). This **may** also occur on brand new Windows Releases, if Microsoft changes its build scheme again, as it did with Windows 10. | ### Agent Setup Script Exit Codes @@ -429,9 +723,9 @@ When you upgrade your N-central Server, you will need to update all your **Deplo 1 - Update the *(Typical)SOAgentVersion* and *(Typical)SOAgentFileVersion* values in the **Partner Configuration** file so the Package will require the new Agent be installed 2 - Replace the existing System-Level Agent Installer in the *(Typical)InstallFolder* with one downloaded from your newly-upgraded N-Central Server (Actions > Download Agent/Probe Software > System Software) -**Don't forget to make sure the *(Typical)SOAgentFileName* matches,** in case SolarWinds MSP ever decides to change the default name of the file! +**Don't forget to make sure the *(Typical)SOAgentFileName* matches,** in case N-Able ever decides to change the default name of the file! -TIP - There are several partners on the SolarWinds MSP discussion forum who have implemented Automation Policies or Scripts that are designed to ease this task, so we strongly recommend that you grab the most suitable one for your needs. +TIP - There are several partners on the N-Able discussion forum who have implemented Automation Policies or Scripts that are designed to ease this task, so we strongly recommend that you grab the most suitable one for your needs. # Credits @@ -444,6 +738,12 @@ Special Thanks go to the following Partners and Community Members for their cont * **Deployment Package** VBScript (InstallAgent.vbs) Version - Optimization, Unified Configuration and Development of Ongoing Updates * Ryan Crowther Jr of RADCOMP Technologies * **Deployment Package** PowerShell (InstallAgent.ps1) Version - Initial Release - -* All Partners and SolarWinds MSP Community Members who have helped and contributed ideas to this **Automation Suite** - +* Robby Swartenbroekx of b-inside + * **Deployment Package** PowerShell/Batch/AMP changes and QA from 5.0.1 to 6.0.0 +* Prejay Shah of Doherty + * **Deployment Package** PowerShell/Batch changes and QA from 5.0.1 to 6.0.0 +* David Brooks of Premier Technology Solutions + * **Deployment Package** PowerShell/Batch/AMP feature updates from 5.0.1 to 6.0.0 and documentation +* Kelvin Tegelaar of Lime Networks + * **AzNableProxy** Created and updated the AzNableProxy service for Azure used in this package +* All Partners and N-Able Community Members who have helped and contributed ideas to this **Automation Suite** diff --git a/ReleaseNotes.md b/ReleaseNotes.md index db8169b..fb86532 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,9 +1,63 @@ +# 2021-05-7 - 6.0.2 +* Fixed bug with Invoke-Webrequest not working due to absence of -UseBasicParsing per [#36](https://github.com/AngryProgrammerInside/InstallAgent/issues/36) +* Added forced removal/cleanup when bad MSI uninstall information or MSI unable to remove old/rogue agent when needed. [#37](https://github.com/AngryProgrammerInside/InstallAgent/issues/37) +* Added WSDL based server verification for environments where outbound ICMP is disabled [#38](https://github.com/AngryProgrammerInside/InstallAgent/issues/38) +* Fixed bug with InstallAgent process not being spun off Async unless -Monitor flag used [#39](https://github.com/AngryProgrammerInside/InstallAgent/issues/39) +* Fixed up Partner Config file validation of True/False attributes + +# 2021-04-12 - 6.0.1 + +## Fixes +* Fixed bug with 64-bit detection on languages other than english +* Fixed bug where agent services would be disabled on Windows 7 / 2008 R2 / PowerShell 2 rather than upgraded +* Removed service disablement during upgrade process +* Fixed registry null values on Windows 7 / 2008 R2 / PowerShell 2 +* Fixed false positive error when script being run offline +* Fixed bug where `switch` type parameter was being tested for boolean values rather than the .IsPresent field +* Fixed bug where logging was being called incorrectly, leading to null values when writing to the event log +* Fixed a bug that it didn't detect all Group Policy installs as such (only detected if run from netlogon folder, not from within the sysvol folder) +* Fixed a but that it incorrected was detected as Group Policy install (now it takes not only the start location but also the user that runs it into account) + +## New Features +* Added option to prevent change of service behavior +* Added Agent AD Status AMP to monitor the installer on a Domain Controller. + +## Housekeeping +* Updated reference for SolarWinds MSP to N-able +* Added an extra registry cleanup for registry items created by version 5.x.x and version 6.0.0 (with the old SolarWinds MSP name) + + +# 2021-02-20 - 6.0.0 +* Registration token install method: + * Activation Key methods for upgrades + * Registration Key methods for new installs/repairs +* Sources for the registration token can include: + * Script input parameters + * A configuration file located in the root of the script folder + * Kelvin Tegelaar's AzNableProxy via an Azure Cloud function also on GitHub under [KelvinTegelaar/AzNableProxy](https://github.com/KelvinTegelaar/AzNableProxy) + * Last successful install configuration saved to a local file +* Functioning N-Central AMP scripts that support 2 methods for updating the configuration file used for installation + * Direct update of Customer ID/Registration Token and other values from N-Central Custom Property (CP) injected via N-Central API See: [How to N-Central API Automation](https://github.com/AngryProgrammerInside/NC-API-Documentation) for examples + * Automatic update of Customer ID/Registration token from values pulled from local Agent/Maintenance XML along with provided JWT (see above documentation) +* Functioning N-Central AMP script to update/renew expired/expiring tokens +* Legacy Support: If you still have old values within your GPO, you can use a flag within the LaunchInstaller.bat to ignore provided parameters and rely upon the configuration file +* Custom installation method data + * Through additional modules you can use your own source for CustomerID/Registration Token enumeration + * A sample module is provided +* Added a new LaunchInstaller.ps1 while still providing LaunchInstaller.bat, either can be used but those wanting to move away from batch files can. +* Optional upload of installation telemetry to Azure Cloud, giving insight into success/failure to help track checkins against N-Central + * Example modules provided +* Quality of Life for development and debugging: + * Added debugmode to the InstallAgent.ps1 to avoid self destruct and reload of modules + * Added debug function to provide Gridviews of common tables + * For more details on development debugging of this script, check out this page on GitHub + # 2019-08-26 ## Fixes and Bug Control * Fixed an issue with the Agent Version comparator, partly due to the bizarre Windows Version numbering method for the Agent Installer - e.g. Version 12.1.2008.0 (12.1 HF1) is "greater than" Version 12.1.10241.0 (12.1 SP1 HF1) * Fixed an issue during Diagnosis phase where incorrect Service Startup Behavior was ALWAYS detected, even after Repairs complete successfully -* The following issues were identified, explored and reported by **Harvey** via SolarWinds MSP Slack (thank you!): +* The following issues were identified, explored and reported by **Harvey** via N-Able MSP Slack (thank you!): * Removed references to the PowerShell 3.0 function **Get-CIMInstance** (from a previous optimization) to maintain PowerShell 2.0 Compatibility * Fixed a premature stop error in the Launcher when a Device has .NET 2.0 SP1 installed, but needs to install PowerShell 2.0 @@ -20,4 +74,4 @@ ## Tweaks and Optimizations -* None this time! +* None this time! \ No newline at end of file diff --git a/media/InstallAgent.pdf b/media/InstallAgent.pdf new file mode 100644 index 0000000..9f9fd47 Binary files /dev/null and b/media/InstallAgent.pdf differ diff --git a/media/InstallAgent.vsdx b/media/InstallAgent.vsdx new file mode 100644 index 0000000..101ed1f Binary files /dev/null and b/media/InstallAgent.vsdx differ diff --git a/media/InstallAgentTokenFlow.vsdx b/media/InstallAgentTokenFlow.vsdx new file mode 100644 index 0000000..3437300 Binary files /dev/null and b/media/InstallAgentTokenFlow.vsdx differ diff --git a/media/debugging-image0.png b/media/debugging-image0.png new file mode 100644 index 0000000..7f6ddd0 Binary files /dev/null and b/media/debugging-image0.png differ diff --git a/media/debugging-image1.png b/media/debugging-image1.png new file mode 100644 index 0000000..6baef29 Binary files /dev/null and b/media/debugging-image1.png differ diff --git a/media/desktop.ini b/media/desktop.ini deleted file mode 100644 index d7c402d..0000000 Binary files a/media/desktop.ini and /dev/null differ diff --git a/media/readme-image0.png b/media/readme-image0.png index 9432a3b..e5b7963 100644 Binary files a/media/readme-image0.png and b/media/readme-image0.png differ diff --git a/media/readme-image1.png b/media/readme-image1.png index 645a3e2..cf9d6b6 100644 Binary files a/media/readme-image1.png and b/media/readme-image1.png differ diff --git a/media/readme-image10.png b/media/readme-image10.png new file mode 100644 index 0000000..f2be970 Binary files /dev/null and b/media/readme-image10.png differ diff --git a/media/readme-image11.png b/media/readme-image11.png new file mode 100644 index 0000000..707f61f Binary files /dev/null and b/media/readme-image11.png differ diff --git a/media/readme-image3.png b/media/readme-image3.png index d4b0e21..e0ef0ea 100644 Binary files a/media/readme-image3.png and b/media/readme-image3.png differ diff --git a/media/readme-image8.png b/media/readme-image8.png new file mode 100644 index 0000000..e94e7af Binary files /dev/null and b/media/readme-image8.png differ diff --git a/media/readme-image9.png b/media/readme-image9.png new file mode 100644 index 0000000..90814e3 Binary files /dev/null and b/media/readme-image9.png differ