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 ConnectivityTestModel() { $this.ComputerName = $($env:COMPUTERNAME) $this.NodeId = $(GetNodeRegistryEntries).NodeId } } 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]$ApiKey [string]$NodeId [string]$ComputerName [string]$IpAddress [string]$ReflexiveIpAddress [string]$NodeVersion [decimal]$Latitude [decimal]$Longitude TeamsAnalyzerNode($DomainName, $ApiKey, $Latitude, $Longitude, $ReflexiveIpAddress, $AllowLocationServices, $AllowPerformanceData) { $this.DomainName = $DomainName $this.ApiKey = $ApiKey $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 { Write-Host "Time left: $($timeLimit - $stopWatch.Elapsed.Seconds)" 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 (!$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 720 -TaskArguments $updateArguments -TaskName $updateTaskName } else { SchedulerServicing -ServicingType Add -Minutes 720 -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}) -ErrorAction SilentlyContinue if ($node.Activated -eq $true) { 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 } 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 = "," $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 "," $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 { $objConnTest.TestPassed = $false TheKragle -Message "There was a problem reaching all transport relays responsible for media traffic." -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 { $counter = 0 Add-Type -AssemblyName System.Device $GeoWatcher = New-Object System.Device.Location.GeoCoordinateWatcher $GeoWatcher.Start() 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(0 c:)\% 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 |