<# .SYNOPSIS Installer script for a remotely remote support client .DESCRIPTION - Downloads a remote support client from the URL provided - Installs the downloaded remote support client with the provided parameters - Intended to be run with administrative permissions, will prompt for elevation for the client install if not .EXAMPLE # Run script locally C:\PS> .\Remote-Support-Install.ps1 # Run script remotely C:\PS> Set-ExecutionPolicy Bypass C:\PS> iex "& { $(irm https://wobig.tech/downloads/scripts/Remote-Support-Install.ps1) }" .NOTES Author: Rick Wobig Source: https://wobig.tech/downloads/scripts/Remote-Support-Install.ps1 #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, HelpMessage = "URL of the remote installer")] [ValidateNotNullOrEmpty()] [string]$DownloadUrl = "https://wobig.tech/downloads/support/wobigtech_remote.exe", [Parameter(Mandatory = $false, HelpMessage = "Organization ID for the support client to join")] [ValidateNotNullOrEmpty()] [string]$OrgId = "0b3d706b-9c5d-41e6-8ae9-5720d16324e6", [Parameter(Mandatory = $false, HelpMessage = "Whether to add a 'Get Support' shortcut to the desktop")] [ValidateNotNullOrEmpty()] [bool]$AddShortcut = $true, [Parameter(Mandatory = $false, HelpMessage = "Device group to join the support client to")] [ValidateNotNullOrEmpty()] [string]$DeviceGroup = "Ungrouped", [Parameter(Mandatory = $false, HelpMessage = "Alias to give to the support client, default to empty")] [ValidateNotNullOrEmpty()] [string]$DeviceAlias = "", [Parameter(Mandatory = $false, HelpMessage = "Server URL to join the support client to")] [ValidateNotNullOrEmpty()] [string]$ServerUrl = "https://support.wobig.tech", [Parameter(Mandatory = $false, HelpMessage = "Testing argument, will show verbose output and pause at certain script locations")] [Switch] [bool]$Testing = $false, [Parameter(Mandatory = $false, HelpMessage = "Root directory where logs are generated/cleaned up")] [ValidateNotNullOrEmpty()] [string]$PathLogs = "$([System.Environment]::GetEnvironmentVariable('TEMP','Machine'))\", [Parameter(Mandatory = $false, HelpMessage = "File prefix for generated files like logs, used for consistency and cleanup")] [ValidateNotNullOrEmpty()] [string]$ScriptFilePrefix = "Script_", [Parameter(Mandatory = $false, HelpMessage = "Threshold in days where we will remove old files")] [ValidateNotNullOrEmpty()] [int]$OldFileFilterDays = 30, [Parameter(Mandatory = $false, HelpMessage = "Force user account for pathing decisions, typically due to ciminstance corruption")] [ValidateNotNullOrEmpty()] [string]$ForceCurrentUser = "" ) #region Script Execution try { # Display help if arg was passed [HouseKeeping]::DisplayHelpIfDesired($DownloadUrl) # Instantiate Logger, display Script Property values for troubleshooting [Logger]::new($Testing, $PathLogs, $ScriptFilePrefix) [Logger]::Logger.LogDisplay("Starting script execution", "Cyan") # Setup client path & download the support client $clientPath = Join-Path -Path $PathLogs -ChildPath "support_client_install.exe" [Client]::Download($DownloadUrl, $clientPath) # Generate a device ID and install the client $deviceId = [Client]::GetNewDeviceId() [Client]::Install($clientPath, $ServerUrl, $OrgId, $DeviceGroup, $DeviceAlias, $deviceId, $AddShortcut) # Finish up [Logger]::Logger.LogDisplay("Stopping script execution", "Cyan") } catch { [HouseKeeping]::StopOnFailure("GLOBAL FAILURE: $($PSItem.ToString())") } #endregion #region Classes class Client { static [string] Download($downloadUrl, $downloadTo){ try{ [Logger]::Logger.LogDisplay("Starting client download from $downloadUrl", "Gray") Invoke-WebRequest -Uri $downloadUrl -OutFile $downloadTo [Logger]::Logger.LogDisplay("Successfully downloaded client to: $downloadTo", "Green") return $downloadTo } catch{ [Logger]::Logger.LogDisplay("FAILURE: $($PSItem.ToString())", "Red") [HouseKeeping]::StopOnFailure("Failed to download client, stopping script execution") return "" } } static [string] GetNewDeviceId(){ $deviceId = (New-Guid).ToString() [Logger]::Logger.Log("Generated new device id: $deviceId") return $deviceId } static [void] Install($clientPath, $serverUrl, $orgId, $deviceGroup, $deviceAlias, $deviceId, $addShortcut){ try{ $installArgs = "-install -quiet -organizationid `"$orgId`" -serverurl `"$serverUrl`" -devicegroup `"$deviceGroup`" -devicealias `"$deviceAlias`" -deviceuuid `"$deviceId`"" if ($addShortcut -eq $true){ $installArgs += " -supportshortcut" } Start-Process $clientPath -ArgumentList $installArgs [Logger]::Logger.LogDisplay("Successfully installed remote support client for device id [$deviceId] connected to [$serverUrl]/[$orgId]", "Green") } catch{ [Logger]::Logger.LogDisplay("FAILURE: $($PSItem.ToString())", "Red") [HouseKeeping]::StopOnFailure("Failed to install client, stopping script execution") } } } class Logger { static [Logger] $Logger [bool] hidden $TestMode [string] hidden $LogPath [string] hidden $FilePrefix [string] hidden $LogDate = "$(Get-Date -Format %M_%d)" Logger( [bool]$testing, [string]$pathLogs, [string]$filePrefix ) { $this.TestMode = $testing $this.LogPath = $pathLogs $this.FilePrefix = $filePrefix [Logger]::Logger = $this [Logger]::GenerateLogDir($this.LogPath) } Log($message) { $methodName = [Logger]::GetMethodName(2) Add-Content "$($this.LogPath)$($this.FilePrefix)$($this.LogDate).log" "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" if ($this.TestMode) { Write-Host -fore DarkGray "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" } } LogPause($message) { if ($this.TestMode) { $methodName = [Logger]::GetMethodName(2) Add-Content "$($this.LogPath)$($this.FilePrefix)$($this.LogDate).log" "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" Write-Host -fore DarkGray "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" Pause } } LogDisplay($message, $color = "Gray"){ $methodName = [Logger]::GetMethodName(2) Add-Content "$($this.LogPath)$($this.FilePrefix)$($this.LogDate).log" "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" Write-Host -fore $color "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" } LogLoading() { if (!($this.TestMode)) { Add-Content "$($this.LogPath)$($this.FilePrefix)$($this.LogDate).log" "." Write-Host -fore DarkGray -NoNewline "." } } static [string] GetMethodName([int]$StackNumber = 1) { return [string]$(Get-PSCallStack)[$StackNumber].FunctionName.ToUpper() } static [string] GenerateLogDir($logDir) { try { if (!(Test-Path -Path $logDir)) { New-Item $logDir -ItemType Directory } return $logDir } catch { Write-Host -fore Red "FAILURE: $($PSItem.ToString())" return $null } } } class HouseKeeping { static [void] DisplayHelpIfDesired($downloadUrl){ # Display help if first arg or $ElevatedUser is ? or help then stop execution if (($downloadUrl -like "?") -or ($downloadUrl -like "help") -or ($downloadUrl -like "/?") -or ($downloadUrl -like "-help") -or ($downloadUrl -like "--help")){ Write-Host "" Write-Host -fore Gree " Syntax: .\Remote-Support-Install.ps1 -DownloadUrl `"https://remote.support.url`"" Write-Host -fore Cyan " -DownloadUrl = URL of the remote installer" Write-Host -fore Cyan " -OrgId = Organization ID for the support client to join" Write-Host -fore Cyan " -AddShortcut = Whether to add a 'Get Support' shortcut to the desktop" Write-Host -fore Cyan " -DeviceGroup = Device group to join the support client to" Write-Host -fore Cyan " -DeviceAlias = Alias to give to the support client, default to empty" Write-Host -fore Cyan " -ServerUrl = Server URL to join the support client to" Write-Host -fore Cyan " -Testing = Testing argument, will show verbose output and pause at certain script locations" Write-Host -fore Cyan " -PathLogs = Root directory where logs are generated/cleaned up" Write-Host -fore Cyan " -ScriptFilePrefix = File prefix for generated files like logs, used for consistency and cleanup" Write-Host -fore Cyan " -OldFileFilterDays = Threshold in days where we will remove old files" Write-Host -fore Cyan " -ForceCurrentUser = Force user account for pathing decisions, typically due to ciminstance corruption" Write-Host "" exit } } static [void] FinishScriptExecution($startTime){ [Logger]::Logger.LogDisplay("Script Finished", "Cyan") $endTime = Get-Date [Logger]::Logger.LogDisplay("Total script time: $(($endTime - $startTime).TotalSeconds)", "Cyan") Exit } static [void] StopOnFailure($exitMessage) { if ($null -ne [Logger]::Logger) { [Logger]::Logger.LogDisplay("$exitMessage | Stopping script execution", "Red") } Exit } static [void] EnsureScriptElevation($currentDirectory){ # Check if the script is running with administrative privileges $isAdmin = [bool]([System.Security.Principal.WindowsIdentity]::GetCurrent()).IsAdmin [Logger]::Logger.Log("Script run as admin: $isAdmin") if (-not $isAdmin) { # Relaunch the script with administrative privileges [Logger]::Logger.Log("Script isn't running with elevated permissions, attempting to elevate") Start-Process powershell.exe -WorkingDirectory $currentDirectory -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs exit } [Logger]::Logger.LogDisplay("Script has admin permissions! Continuing script execution", "Green") } } class IO { static [bool] RemoveFolderIfExist($folderPath){ try { if (Test-Path -PathType Container $folderPath) { Remove-Item $folderPath -Recurse -Force -ErrorAction SilentlyContinue [Logger]::Logger.Log("Removed folder recursively: $folderPath") return $true } else { [Logger]::Logger.Log("Folder doesn't exist, skipping removal: $folderPath") return $true } } catch { [Logger]::Logger.LogDisplay("FAILURE: $($PSItem.ToString())", "Red") return $false } } static [bool] CreateFolderIfNotExist($folderPath){ # Create folder path if it doesn't exist if (!(Test-Path -PathType Container $folderPath)) { try { New-Item -Path $folderPath -ItemType "directory" | Out-Null [Logger]::Logger.Log("Created non-existant folder: $folderPath") return $true } catch { [Logger]::Logger.LogDisplay("FAILURE: $($PSItem.ToString())", "Red") return $false } } return $false } } #endregion