TeamsAnalyzerNode.psm1
#region INITIAL VARIABLES [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 [string]$ProductName = "TeamsAnalyzerNode" [string]$modulePath = (get-module $ProductName -ListAvailable).modulebase [string]$toolSourcePath = $modulePath + "\MicrosoftSkypeForBusinessNetworkAssessmentTool.exe" [string]$toolInstallArgs = "/install /quiet /norestart /log $env:TEMP\ToolInstall.txt" [string]$toolUninstallArgs = "/uninstall /quiet /norestart /log $env:TEMP\ToolUninstall.txt" [string]$toolName = "Microsoft Skype for Business Network Assessment Tool" [string]$networkAssessmentToolPath = "${env:ProgramFiles(x86)}\Microsoft Skype for Business Network Assessment Tool\NetworkAssessmentTool.exe" [string]$networkAssessmentToolConfig = "${env:ProgramFiles(x86)}\Microsoft Skype for Business Network Assessment Tool\NetworkAssessmentTool.exe.config" [string]$networkAssessmentToolWorkingDirectory = "${env:ProgramFiles(x86)}\Microsoft Skype for Business Network Assessment Tool\" [string]$QualityResultsFilePath = "$($env:ProgramFiles)\TeamsAnalyzer\TeamsAnalyzerNode\quality_results.csv" [string]$ConnectivityResultsFilePath = "$($env:ProgramFiles)\TeamsAnalyzer\TeamsAnalyzerNode\connectivity_results.txt" [string]$ResultsFilePath = "$($env:ProgramFiles)\TeamsAnalyzer\TeamsAnalyzerNode\" [string]$taskIdentity = "SYSTEM" [string]$taskPath = "\TeamsAnalyzerNode\" [string]$taskAction = "PowerShell.exe" [string]$qualityCaptureTaskName = 'Teams Analyzer Node (Quality Test)' [string]$connectivityTestTaskName = 'Teams Analyzer Node (Connectivity Test)' [string]$controllerTaskName = 'Teams Analyzer Node (Controller)' [string]$updateTaskName = 'Teams Analyzer Node (Updater)' [string]$qualityArguments = '-NoProfile -WindowStyle Hidden -Command "& {Invoke-TaQualityTest}"' [string]$connectivityArguments = '-NoProfile -WindowStyle Hidden -Command "& {Invoke-TaConnectivityTest}"' [string]$controllerArguments = '-NoProfile -WindowStyle Hidden -Command "& {Invoke-TaController}"' [string]$updateArguments = '-NoProfile -WindowStyle Hidden -Command "&{Invoke-TaNodeUpgrade}"' [string]$keyPath = "HKLM:\SOFTWARE\TeamsAnalyzer\TeamsAnalyzerNode" [string]$baseUrl = "https://api.teamsanalyzer.com/beta" #endregion #region CLASSES class AudioTest { [string]$DomainName [string]$NodeId [string]$DialogId [string]$ComputerName [string]$PacketLossRate [string]$RoundTripLatencyInMs [string]$PacketsSent [string]$PacketsReceived [string]$AverageJitterInMs [string]$PacketReorderRatio [string]$IpAddress [string]$ReflexiveIpAddress [decimal]$Latitude [decimal]$Longitude [int]$PercentCpu [decimal]$PercentDiskTime AudioTest($keys, $csvCallData, $deviceLocation, $perfData, $reflexiveIpAddress) { $this.DomainName = $keys.DomainName $this.NodeId = $keys.NodeId $this.DialogId = [guid]::NewGuid() $this.ComputerName = $($env:COMPUTERNAME) $this.IpAddress = $((Get-NetIPConfiguration | Where-Object {$_.IPv4DefaultGateway -ne $null -and $_.NetAdapter.Status -ne "Disconnected"}).IPv4Address.IPAddress) $this.ReflexiveIpAddress = $reflexiveIpAddress $this.PacketLossRate = $csvCallData.PacketLossRate $this.RoundTripLatencyInMs = $csvCallData.RoundTripLatencyInMs $this.PacketsSent = $csvCallData.PacketsSent $this.PacketsReceived = $csvCallData.PacketsReceived $this.AverageJitterInMs = $csvCallData.AverageJitterInMs $this.PacketReorderRatio = $csvCallData.PacketReorderRatio $this.Latitude = $deviceLocation.Latitude $this.Longitude = $deviceLocation.Longitude $this.PercentCpu = $perfData.PercentCpu $this.PercentDiskTime = $perfData.PercentDiskTime } } class ConnectivityTestModel { [string]$ComputerName [string]$NodeId [bool]$TestPassed [array]$ConnectivityErrors ConnectivityTestModel() { $this.ComputerName = $($env:COMPUTERNAME) $this.NodeId = $(GetNodeRegistryEntries).NodeId } Add($errorItem){ $this.ConnectivityErrors += ,$errorItem } } class KragleLogger { [string]$DomainName [guid]$NodeId [string]$ComputerName [string]$Message [string]$EventId [string]$LogName [string]$EntryType KragleLogger($keys, $Message, $EventId, $LogName, $EntryType) { $this.DomainName = $keys.DomainName $this.NodeId = $keys.NodeId $this.ComputerName = $($env:COMPUTERNAME) $this.Message = $Message $this.EventId = $EventId $this.LogName = $LogName $this.EntryType = $EntryType } } class TeamsAnalyzerNode { [string]$DomainName [string]$NodeId [string]$ComputerName [string]$IpAddress [string]$ReflexiveIpAddress [string]$NodeVersion [decimal]$Latitude = 0.00 [decimal]$Longitude = 0.00 TeamsAnalyzerNode($DomainName, $ApiKey, $Latitude, $Longitude, $ReflexiveIpAddress, $AllowLocationServices, $AllowPerformanceData) { $this.DomainName = $DomainName $this.NodeId = [guid]::NewGuid() $this.ComputerName = $($env:COMPUTERNAME) $this.IpAddress = $((Get-NetIPConfiguration | Where-Object {$_.IPv4DefaultGateway -ne $null -and $_.NetAdapter.Status -ne "Disconnected"} -ErrorAction SilentlyContinue).IPv4Address.IPAddress) $this.ReflexiveIpAddress = $ReflexiveIpAddress $this.NodeVersion = $(Get-Module -ListAvailable TeamsAnalyzerNode).Version.ToString() $this.Latitude = $Latitude $this.Longitude = $Longitude } } #endregion #region EXPORTED FUNCTIONS function Invoke-TaQualityTest { TheKragle -Message "Starting audio quality test on $($env:COMPUTERNAME) at $(Get-Date)." -EntryType Information -LogName Application -EventId 601 -DoNotPostToTenant #set maximum time limit for the process to run. It should only take about 17s to execute $stopWatch = [Diagnostics.Stopwatch]::StartNew() [int]$timeLimit = 60 $assessmentInstance = Start-Process -FilePath $networkAssessmentToolPath -WorkingDirectory $networkAssessmentToolWorkingDirectory -NoNewWindow -PassThru do { Start-Sleep -Milliseconds 250 } until (($assessmentInstance.HasExited -eq $true) -or ($stopWatch.Elapsed.TotalSeconds -ge $timeLimit)) $stopWatch.Stop() #did it complete or did we have to exit? if ($assessmentInstance.HasExited) { TheKragle -Message "Completed audio quality test on $($env:COMPUTERNAME) at $(Get-Date)." -EntryType Information -LogName Application -EventId 602 -DoNotPostToTenant ProcessCallData } else { #kill it $assessmentInstance.Kill() TheKragle -Message "The audio quality test took longer than $($timeLimit) to complete and was killed." -EntryType Error -LogName Application -EventId 610 break } } function Invoke-TaConnectivityTest { #set maximum time limit for the process to run. It should only take about 17s to execute $stopWatch = [Diagnostics.Stopwatch]::StartNew() [int]$timeLimit = 120 #2mins limit $connTestArgs = "/connectivitycheck /verbose" TheKragle -Message "Starting transport relay connectivity test." -EntryType Information -LogName Application -EventId 705 -DoNotPostToTenant $assessmentInstance = Start-Process -FilePath $networkAssessmentToolPath -WorkingDirectory $networkAssessmentToolWorkingDirectory -ArgumentList $connTestArgs -NoNewWindow -PassThru do { Start-Sleep -Milliseconds 200 } until (($assessmentInstance.HasExited -eq $true) -or ($stopWatch.Elapsed.TotalSeconds -ge $timeLimit)) $stopWatch.Stop() #did it complete or did we have to exit? if ($assessmentInstance.HasExited) { ProcessConnectivityData } else { #kill it $assessmentInstance.Kill() TheKragle -Message "The network assessment tool's connectivity check took longer than $timeLimit to complete and was killed." -EntryType Error -LogName Application -EventId 701 } } function New-TaNetworkNode { [CmdletBinding()] param( [parameter(Mandatory=$true)]$ApiKey, [parameter(Mandatory=$true)]$DomainName, [parameter(Mandatory=$false)]$AutoUpdateNode = $true ) begin { if ([system.version](Get-CimInstance -ClassName Win32_OperatingSystem).Version -lt [system.version]6.2) { Write-Error -Message "This module requires Windows 8 or higher." break } if (!$ApiKey -or !$DomainName) { Write-Error -Message "Please specify both a DomainName and ApiKey variable." break } if ((IsAdministrator) -eq $false) { Write-Error -Message "Please re-run this command in an (Administrator) elevated prompt." break } $keys = GetNodeRegistryEntries if ($keys.ApiKey -or $keys.DomainName -or $keys.NodeId){ Write-Error -Message "A previous installation of the node was detected. Please remove the node first by running 'Remove-TaNetworkNode'." break } try { #get Tenant settings $tenantSettings = Invoke-RestMethod -Uri $($BaseUrl + "/Tenant/" + $DomainName) -Method Get -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $ApiKey}) if (!$tenantSettings){ Write-Error -Message "Could not retrieve tenant settings. Please check your Internet connection and try again." break } } catch { Write-Error -Message "There was a problem executing the REST method to retrieve the tenant settings." break } if ($tenantSettings.AllowLocationServices -eq $true){ $deviceLocation = GetDeviceLocation $publicIP = GetPublicIp } } process { try { #install the SFB NAT tool itself ToolServicing -ServicingType Install #customize the NetworkAssessmentTool.exe.config file ToolConfig $newNode = [TeamsAnalyzerNode]::New($DomainName, $ApiKey, $deviceLocation.Latitude, $deviceLocation.Longitude, $publicIP, $tenantSettings.AllowLocationServices, $tenantSettings.AllowPerformanceData) New-Item -Path $KeyPath -Force | Out-Null New-ItemProperty -Path $KeyPath -Name "DomainName" -Value $DomainName | Out-Null New-ItemProperty -Path $KeyPath -Name "ApiKey" -Value $ApiKey | Out-Null New-ItemProperty -Path $KeyPath -Name "NodeId" -Value $newNode.NodeId | Out-Null New-ItemProperty -Path $KeyPath -Name "AllowLocationServices" -Value $tenantSettings.AllowLocationServices -PropertyType Dword | Out-Null New-ItemProperty -Path $KeyPath -Name "AllowPerformanceData" -Value $tenantSettings.AllowPerformanceData -PropertyType Dword | Out-Null Write-Verbose -Message "Successfully created registry entries on $($newNode.ComputerName) for $($DomainName)." -Verbose } catch { Write-Error -Message "Could not create new network node. Please contact support." Write-Verbose -Message "Removing registry entries." -Verbose Remove-Item -Path "HKLM:\SOFTWARE\TeamsAnalyzer" -Recurse -Confirm:$false throw } # NODE REGISTRATION # try { Invoke-RestMethod -Uri $($baseUrl + "/NetworkNode/" + $newNode.DomainName + "/" + $newNode.NodeId + "/Register") -Method Post -Body $($newNode | ConvertTo-Json) -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $ApiKey}) | Out-Null TheKragle -Message "Successfully registered new node $($newNode.NodeId) for $($newNode.DomainName) on $(Get-Date)." -EntryType Information -LogName Application -EventId 210 } catch { TheKragle -Message "There was a problem generating a new object for the node $($newNode.NodeId) for $($newNode.DomainName)." -EntryType Error -LogName Application -EventId 505 throw } #add scheduled task for calling, connectivity, controller tests SchedulerServicing -ServicingType Add -Minutes 2 -TaskArguments $controllerArguments -TaskName $controllerTaskName SchedulerServicing -ServicingType Add -Minutes 5 -TaskArguments $qualityArguments -TaskName $qualityCaptureTaskName SchedulerServicing -ServicingType Add -Minutes 120 -TaskArguments $connectivityArguments -TaskName $connectivityTestTaskName if ($AutoUpdateNode) { SchedulerServicing -ServicingType Add -Minutes 45 -TaskArguments $updateArguments -TaskName $updateTaskName } else { SchedulerServicing -ServicingType Add -Minutes 45 -TaskArguments $updateArguments -TaskName $updateTaskName -Enabled:$false } } end{} } function Remove-TaNetworkNode { [CmdletBinding()] param( [switch]$Force ) if ((IsAdministrator) -eq $false) { Write-Warning -Message "Please re-run this command in an (Administrator) elevated prompt." break } $keys = GetNodeRegistryEntries if (!$keys.DomainName -eq $null -and !$keys.ApiKey -and !$keys.NodeId) { Write-Error -Message "This node appears to not be installed correctly or it is missing critical Registry entries." break } #check if node has been deactivated or not and quit if -Force hasn't been specified if ($Force -eq $false) { $node = Invoke-RestMethod -Uri $($BaseUrl + "/NetworkNode/" + $keys.DomainName + "/" + $keys.NodeId) -Method Get -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $keys.ApiKey}) if ($node.Activated -ne $false) { TheKragle -Message "An attempt was made to remove this network node installation without first deactivating it. Please deactivate this node before attempting to remove it, or specify the -Force switch on the Remove-TaNetworkNode command." -EntryType Error -LogName Application -EventId 413 break } } ToolServicing -ServicingType Uninstall SchedulerServicing -ServicingType Remove -TaskName $qualityCaptureTaskName SchedulerServicing -ServicingType Remove -TaskName $connectivityTestTaskName SchedulerServicing -ServicingType Remove -TaskName $controllerTaskName SchedulerServicing -ServicingType Remove -TaskName $updateTaskName try { Write-Verbose -Message "Attempting to remove network node from website using REST API." Invoke-RestMethod -Uri $($BaseUrl + '/NetworkNode/' + $keys.DomainName + "/" + $keys.NodeId + "/Delete") -Method Post -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $keys.ApiKey}) | Out-Null TheKragle -Message "Successfully removed node from the website at $(Get-Date)." -EntryType Information -LogName Application -EventId 211 -DoNotPostToTenant } catch { TheKragle -Message "There was a problem removing node $($keys.NodeId) from $($env:COMPUTERNAME). The error was $($_.Exception.Message)." -EntryType Information -LogName Application -EventId 409 } TheKragle -Message "Removing local registry entries from $($env:COMPUTERNAME)." -EntryType Information -LogName Application -EventId 208 -DoNotPostToTenant Remove-Item -Path "HKLM:\SOFTWARE\TeamsAnalyzer" -Recurse -Confirm:$false -ErrorAction SilentlyContinue } function Invoke-TaNodeUpgrade { [CmdletBinding()] param() $keys = GetNodeRegistryEntries $module = Get-InstalledModule -Name $ProductName -AllVersions if ($module -is [array]) { for ($i = 0; $i -le $module.Count -2; $i++) { #remove old modules before upgrade as there should only be one. #since the index is zero-based, we need to subtract two so that we don't remove the current, most recent version. try { Uninstall-Module $ProductName -RequiredVersion $module[$i].Version -Force -Confirm:$false -Verbose TheKragle -Message "Successfully removed module version $($module[$i].Version.ToString())." -EntryType Information -LogName Application -EventId 214 } catch { TheKragle -Message "There was a problem removing version $($module[$i].Version.ToString())" -EntryType Error -LogName Application -EventId 508 } } #get most recent version [system.version]$moduleVersion = ($module[0]).Version } else { [system.version]$moduleVersion = (Get-Module -Name $ProductName -ListAvailable).Version } if (!$moduleVersion) { TheKragle -Message "Could not determine the local version while checking for updates." -EntryType Error -LogName Application -EventId 417 break } [system.version]$onlineVersion = (Find-Module -Name $ProductName).Version if (!$onlineVersion) { TheKragle -Message "Could not determine the online version while checking for updates." -EntryType Error -LogName Application -EventId 416 break } if ($moduleVersion -lt $onlineVersion) { TheKragle -Message "New version found! Upgrading from $($moduleVersion) to $($onlineVersion)." -EntryType Information -LogName Application -EventId 212 Install-Module -Name $ProductName -RequiredVersion $onlineVersion -AllowClobber -Confirm:$false -Force -Verbose try { if (!(Get-Module $ProductName -ListAvailable | Where-Object Version -eq $onlineVersion)) { TheKragle -Message "Although a new version of the PowerShell module was detected, there was a problem updating it." -EntryType Error -LogName Application -EventId 507 break } #update node properties $body = @{ "NodeId" = $keys.NodeId "NodeVersion" = $onlineVersion.ToString() } Invoke-RestMethod -Uri $($BaseUrl + "/NetworkNode/" + $keys.DomainName) -Method Put -Body $($body | ConvertTo-Json) -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $keys.ApiKey}) TheKragle -Message "Successfully upgraded PowerShell module from $($moduleVersion) to $($onlineVersion)." -EntryType Information -LogName Application -EventId 299 #customize the NetworkAssessmentTool.exe.config file just to make sure ToolConfig #clean up old versions # $allVersions = Get-InstalledModule -Name $ProductName -AllVersions # $toRemove = $allVersions | Where-Object {$_.Version -ne $onlineVersion} } catch { TheKragle -Message "There was a problem updating the PowerShell module. The error was: $($_exception.Message)." -EntryType Error -LogName Application -EventId 506 break } } else { TheKragle -Message "Automatic upgrade check complete. The detected version online was $($onlineVersion) and the installed version is $($moduleVersion)." -EntryType Information -LogName Application -EventId 213 } } function Invoke-TaController { [CmdletBinding()] param() try { $keys = GetNodeRegistryEntries } catch { TheKragle -Message "There was a problem getting registry keys for the application. Please try removing and re-installing this module." -EntryType Error -LogName Application -EventId 411 -DoNotPostToTenant break } try { $tenantSettings = Invoke-RestMethod -Uri $($baseUrl + "/Tenant/" + $keys.DomainName) -Method Get -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $keys.ApiKey}) $node = Invoke-RestMethod -Uri $($baseUrl + "/NetworkNode/" +$keys.DomainName + "/" + $keys.NodeId) -Method Get -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $keys.ApiKey}) Write-Verbose -Message "Results from website query for this node: $($node)" } catch { TheKragle -Message "There was a problem connecting to the web API to get the settings for this node. Please contact us using the email address on the website for assistance." -EntryType Error -LogName Application -EventId 412 break } #set performance/location registry entries if ($tenantSettings) { Set-ItemProperty -Path $keyPath -Name "AllowLocationServices" -Value $tenantSettings.AllowLocationServices | Out-Null Set-ItemProperty -Path $keyPath -Name "AllowPerformanceData" -Value $tenantSettings.AllowPerformanceData | Out-Null } #check for node activation through WebAPI if ($node.Activated) { Get-ScheduledTask -TaskName $qualityCaptureTaskName | Enable-ScheduledTask | Out-Null Get-ScheduledTask -TaskName $connectivityTestTaskName | Enable-ScheduledTask | Out-Null } else { Get-ScheduledTask -TaskName $qualityCaptureTaskName | Disable-ScheduledTask | Out-Null Get-ScheduledTask -TaskName $connectivityTestTaskName | Disable-ScheduledTask | Out-Null } try { Write-Verbose "Setting trigger interval for Quality task to $($node.QualityTestFrequency) minutes." SchedulerServicing -ServicingType Update -Minutes $node.QualityTestFrequency -TaskArguments $qualityArguments -TaskName $qualityCaptureTaskName | Out-Null Write-Verbose "Setting trigger interval for Connectivity task to $($node.ConnectivityTestFrequency) minutes." SchedulerServicing -ServicingType Update -Minutes $node.ConnectivityTestFrequency -TaskArguments $connectivityArguments -TaskName $connectivityTestTaskName | Out-Null } catch { TheKragle -Message "There was a problem modifying the scheduled tasks on $($env:COMPUTERNAME) at $(Get-Date). $($_.Exception.Message)" -EntryType Warning -LogName Application -EventId 410 } } #endregion #region INTERNAL FUNCTIONS function GetNodeRegistryEntries { $keys = (Get-ItemProperty -Path $keyPath -ErrorAction SilentlyContinue) return [PSCustomObject]@{ DomainName = $keys.DomainName ApiKey = $keys.ApiKey NodeId = $keys.NodeId AllowLocationServices = $keys.AllowLocationServices AllowPerformanceData = $keys.AllowPerformanceData NewNodeWebHook = $keys.NewNodeWebHook NodeAlertWebHook = $keys.NodeAlertWebHook } } function TheKragle { [CmdletBinding()] param( [parameter(Mandatory)][string]$Message, [parameter(Mandatory)][string][validateset("Error","Information","Warning")]$EntryType, [parameter(Mandatory)][string][validateset("Application","Security","System")]$LogName, [parameter(Mandatory)][int]$EventId, [parameter()][switch]$DoNotPostToTenant ) try { New-EventLog -LogName Application -Source $ProductName -ErrorAction SilentlyContinue Write-Verbose -Message $Message -Verbose Write-EventLog -LogName $LogName -Source $ProductName -EntryType $EntryType -Category 0 -EventId $EventId -Message $Message -ErrorAction SilentlyContinue -ErrorVariable errVar } catch { Write-Error $_.Exception.Message } if (!$DoNotPostToTenant.IsPresent) { try { $keys = GetNodeRegistryEntries $kraggleObject = [KragleLogger]::new($keys, $Message, $EventId, $LogName, $EntryType) $activityPostResult = Invoke-RestMethod -Uri $($baseUrl + "/ActivityLog/" + $keys.DomainName + "/" + $keys.NodeId) -Method Post -Body $($kraggleObject | ConvertTo-Json) -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $keys.ApiKey}) Write-Verbose -Message $activityPostResult } catch { Write-EventLog -LogName $LogName -Source $productName -EntryType Error -Category 0 -EventId 1000 -Message "Error executing logging activity on $($env:COMPUTERNAME) at $(Get-Date). $($_.Exception.Message)." } } } function IsAdministrator { $user = [Security.Principal.WindowsIdentity]::GetCurrent(); if ((New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { return $true } else { return $false } } function GetSaveLocation { #read xml file to get save location for call tests and connectivity tests [xml]$xmlFile = Get-Content -Path $networkAssessmentToolConfig [array]$saveLocations = [PSCustomObject][ordered]@{ AudioTestSavePath = ($xmlFile.Configuration.Appsettings.Add | Where-Object Key -eq ResultsFilePath).value ConnectionTestSavePath = ($xmlFile.Configuration.Appsettings.Add | Where-Object Key -eq OutputFilePath).value } return $saveLocations } function ToolServicing { [CmdletBinding()] param( [parameter()][validateset("Install","Uninstall")]$ServicingType) begin{} process { if ($ServicingType -eq "Uninstall") { #is it installed already? $installedResult = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object {$_.DisplayName -eq $toolName -and $_.BundleProviderKey -ne $null} if (!$installedResult) { #not found return } else { #yes, remove it first TheKragle -Message "Attempting to remove $($toolName) from $($env:COMPUTERNAME)." -EntryType Information -LogName Application -EventId 209 try { $uninstallResult = Start-Process -FilePath $installedResult.BundleCachePath -ArgumentList $toolUninstallArgs -NoNewWindow -PassThru do { Write-Verbose "Waiting for the tool uninstall to complete..." Start-Sleep -Seconds 3 } while ($uninstallResult.HasExited -eq $false) } catch { Write-Error -Message "Could not remove $($toolName) using BundleCachePath property." throw } $installedResult = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object {$_.DisplayName -eq $toolName -and $_.BundleProviderKey -ne $null} if (!$installedResult) { TheKragle -Message "Successfully removed $($toolName) from $($env:COMPUTERNAME)." -EntryType Information -LogName Application -EventId 207 } else { throw } } #clean up saved test results directory if the default was used (the user didn't specify a location) if ([System.IO.Directory]::Exists((Split-Path -Path $ResultsFilePath))) { try { Write-Verbose "Attempting to remove $ResultsFilePath..." [System.IO.Directory]::Delete((Split-Path -Path $ResultsFilePath), $true) Write-Verbose "Successfully removed $ResultsFilePath." } catch [System.Management.Automation.MethodInvocationException] { $msg = "Could not remove $($ResultsFilePath). This may be due to an explorer or command window locking the location from deletion." Write-Error -Message $msg } } } if ($ServicingType -eq "Install") { if (!(Test-Path -Path $ResultsFilePath)) { New-Item -ItemType Directory -Path $ResultsFilePath -ErrorAction SilentlyContinue | Out-Null if (!(Get-Item -Path $ResultsFilePath)) { TheKragle -Message "There was a problem creating the directory $($ResultsFilePath) on $($env:COMPUTERNAME)." -EntryType Error -LogName Application -EventId 406 } } if ($installedResult = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object DisplayName -eq $toolName){ TheKragle -Message "A request was made to install the $($toolName) however it was already detected. Please remove the tool first by running 'Remove-TaNetworkNode'." -EntryType Error -LogName Application -EventId 502 break } if (!(Test-Path $toolSourcePath)) { TheKragle -Message "Could not find the source files for $($toolName) in $($toolSourcePath)" -EntryType Error -LogName Application -EventId 501 throw } try { TheKragle -Message "Attempting to install the $($toolName) on $($env:COMPUTERNAME)." -EntryType Information -LogName Application -EventId 201 $installProcess = Start-Process -FilePath $toolSourcePath -ArgumentList $toolInstallArgs -NoNewWindow -PassThru do { Start-Sleep -Seconds 3 } while ($installProcess.HasExited -eq $false) } catch { TheKragle -Message "There was a problem installing the $($toolName). The exception was: $($_.Exception.Message)." -EntryType Error -LogName Application -EventId 503 throw } #need to verify it has been installed $installedResult = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object DisplayName -eq $toolName if ($installedResult) { TheKragle -Message "The $($toolName) was installed successfully." -EntryType Information -LogName Application -EventId 202 } else { TheKragle -Message "The $($toolName) installation was attempted. There was no specific error, however the installation could not be verified." -EntryType Warning -LogName Application -EventId 404 } } } end{} } function SchedulerServicing { [CmdletBinding()] param( [parameter(Mandatory = $true)][validateset("Add","Remove","Update")][string]$ServicingType, [parameter(Mandatory = $false)][int]$Minutes, [parameter()]$TaskArguments, [parameter()]$TaskName, [parameter()][bool]$Enabled = $true ) begin {} process { $timeSpan = New-TimeSpan -Minutes $Minutes $scheduledTask = Get-ScheduledTask -TaskName $TaskName -TaskPath $taskPath -ErrorAction SilentlyContinue #create scheduled task switch ($ServicingType) { "Add" { if ($scheduledTask){ TheKragle -Message "An attempt was made to add a scheduled task which already exists. The task name is: $($taskName)." -EntryType Error -LogName Application -EventId 401 break } $action = New-ScheduledTaskAction -Execute $taskAction -Argument $TaskArguments $trigger = New-ScheduledTaskTrigger -At (Get-Date) -Once -RepetitionInterval $timeSpan if ($Enabled) { $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) } else { $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) -Disable } $registration = Register-ScheduledTask -TaskPath $taskPath -TaskName $TaskName -Settings $settings -Action $action -Trigger $trigger -User $taskIdentity -RunLevel Highest -ErrorAction SilentlyContinue #should return .State = "Ready" if ($registration.State -eq "Ready") { TheKragle -Message "Successfully registered scheduled task: $($taskName) to run every $($minutes) minute(s) indefinitely." -EntryType Information -LogName Application -EventId 203 -DoNotPostToTenant } else { TheKragle -Message "Could not create scheduled task for task name: $($taskName) on $($env:COMPUTERNAME)" -EntryType Error -LogName Application -EventId 504 } } "Remove" { if (!$scheduledTask) { #does not exist TheKragle -Message "The scheduled task $($taskName) was not found on this system. It may have been removed already." -EntryType Warning -LogName Application -EventId 402 } else { Unregister-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Confirm:$false -ErrorAction SilentlyContinue TheKragle -Message "Successfully removed scheduled task: $($taskName) on $($env:COMPUTERNAME)." -EntryType Information -LogName Application -EventId 204 } #verify removal $scheduledTask = Get-ScheduledTask -TaskName $taskName -TaskPath $taskPath -ErrorAction SilentlyContinue if ($scheduledTask){ TheKragle -Message "There was a problem un-registering the scheduled task: $($taskName) from $($env:COMPUTERNAME)." -EntryType Warning -LogName Application -EventId 405 } } "Update" { $scheduledTask.Triggers.Repetition.Interval = "PT$($Minutes)M" Set-ScheduledTask -InputObject $scheduledTask } Default {} } } end {} } function ToolConfig { #alter the default config file settings during installation. later we'll grab settings from the cloud set by an admin user. try { [xml]$xmlFile = Get-Content -Path $networkAssessmentToolConfig #Quality test output location ($xmlFile.Configuration.Appsettings.Add | Where-Object Key -eq ResultsFilePath).value = $QualityResultsFilePath #Connectivity test output location ($xmlFile.Configuration.Appsettings.Add | Where-Object Key -eq OutputFilePath).value = $ConnectivityResultsFilePath #Delimiter for quality results ($xmlFile.Configuration.Appsettings.Add | Where-Object Key -eq Delimiter).Value = "`t" $xmlFile.Save($networkAssessmentToolConfig) } catch { TheKragle -Message "Error saving $($networkAssessmentToolConfig)." -EntryType Error -LogName Application -EventId 407 } TheKragle -Message "Completed customization of the config file: $($networkAssessmentToolConfig)." -EntryType Information -LogName Application -EventId 205 } function ProcessCallData { $keys = GetNodeRegistryEntries #check registry to see if we can get physical location if ($keys.AllowLocationServices -eq 1) { $deviceLocation = GetDeviceLocation $reflexiveIpAddress = GetPublicIp } #check registry to see if we can collect perf data if ($keys.AllowPerformanceData -eq 1) { $perfData = GetPerformanceData } try { $csvCallData = Get-Content -Path $QualityResultsFilePath | ConvertFrom-Csv -Delimiter "`t" $csvCallData.PSObject.Properties | ForEach-Object { $_.Value = $_.Value.Replace(",",".") } $audioTest = [AudioTest]::new($keys, $csvCallData, $deviceLocation, $perfData, $reflexiveIpAddress) } catch { TheKragle -Message $_.Exception.Message -EntryType Error -LogName Application -EventId 999 throw } try { Invoke-RestMethod -Uri $($baseUrl + "/AudioTest/" + $keys.DomainName + "/" + $keys.NodeId) -Method Post -Body $($audioTest | ConvertTo-Json) -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $keys.ApiKey}) TheKragle -Message "Successfully posted audio test to tenant for $($env:COMPUTERNAME) at $(Get-Date)." -EntryType Information -LogName Application -EventId 603 -DoNotPostToTenant } catch { TheKragle -Message "There was a problem posting the audio test. The error was: $($_.Exception.Message)." -EntryType Error -LogName Application -EventId 611 throw } } function ProcessConnectivityData { $keys = GetNodeRegistryEntries $objConnTest = [ConnectivityTestModel]::new() try { $connTestResults = Get-Content -Path $ConnectivityResultsFilePath } catch { TheKragle -Message "Could not read connectivity results data from $($ConnectivityResultsFilePath)" -EntryType Error -LogName Application -EventId 702 throw } if ($connTestResults.Contains("Verifications completed successfully")) { $objConnTest.TestPassed = $true TheKragle -Message "Connectivity test completed successfully. All transport relays are reachable on all required ports." -EntryType Information -LogName Application -EventId 703 } else { #parse results file $objConnTest.TestPassed = $false $regexConn = "Please check if.*" foreach($line in Get-Content -Path $ConnectivityResultsFilePath) { if($line -match $regexConn){ $objConnTest.Add($line) } } TheKragle -Message $("There was a problem reaching all transport relays responsible for media traffic. " + $objConnTest.ConnectivityErrors -join ",") -EntryType Error -LogName Application -EventId 703 } Invoke-RestMethod -Uri $($baseUrl + "/ConnectivityTest/" + $keys.DomainName + "/" + $keys.NodeId) -Method Post -Body $($objConnTest | ConvertTo-Json) -ContentType 'application/json' -Headers $(@{'Ocp-Apim-Subscription-Key' = $keys.ApiKey}) Remove-Item -Path $ConnectivityResultsFilePath -Force -Confirm:$false } function GetDeviceLocation { Add-Type -AssemblyName System.Device $GeoWatcher = New-Object System.Device.Location.GeoCoordinateWatcher $GeoWatcher.Start() if ($GeoWatcher.Permission -ne "Granted") { Write-Verbose -Message "Permission to use Windows Location Services was denied. Click the start menu, settings, and search for 'location' to enable it. You will need to remove this node and install it again to obtain the node's location." -Verbose break } $counter = 0 while (($GeoWatcher.Status -ne "Ready") -and ($GeoWatcher.Permission -ne "Denied") -or $counter -ge 5000) { Start-Sleep -Milliseconds 100 $counter ++ } return $GeoWatcher.Position.Location } function GetPerformanceData { return [PSCustomObject]@{ PercentDiskTime = $(("\PhysicalDisk(_total)\% Disk Time" | Get-Counter).CounterSamples.CookedValue) PercentCpu = $(Get-WmiObject Win32_Processor | Measure-Object -Property LoadPercentage -Average | Select-Object -ExpandProperty Average) } } function GetPublicIp { try { return Invoke-RestMethod http://ipinfo.io/json -ErrorAction SilentlyContinue | Select-Object -exp ip } catch { #probably over daily limit of queries to this free service } } #endregion |