mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-14 21:59:20 +00:00
304 lines
12 KiB
PowerShell
304 lines
12 KiB
PowerShell
# Copyright (c) Microsoft Corporation.
|
|
# Licensed under the MIT License.
|
|
|
|
# unit tests for telemetry
|
|
# these tests aren't going to check that telemetry is being sent
|
|
# only that we're not treating the telemetry.uuid file correctly
|
|
|
|
function Get-OSTelemetryLevel {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns the effective Windows Telemetry level (0-3).
|
|
Logic: Checks GPO overrides, then System preferences, then defaults to 1.
|
|
#>
|
|
|
|
$gpoPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection"
|
|
$sysPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection"
|
|
$valueName = "AllowTelemetry"
|
|
|
|
# 1. Check the "Managed" Policy (Group Policy)
|
|
if (Test-Path $gpoPath) {
|
|
$gpoValue = Get-ItemProperty -Path $gpoPath -Name $valueName -ErrorAction SilentlyContinue
|
|
if ($gpoValue -and $gpoValue.$valueName) {
|
|
return [int]$gpoValue.$valueName
|
|
}
|
|
}
|
|
|
|
# 2. Check the "User/System" Preference (Settings App)
|
|
if (Test-Path $sysPath) {
|
|
$sysValue = Get-ItemProperty -Path $sysPath -Name $valueName -ErrorAction SilentlyContinue
|
|
if ($sysValue -and $sysValue.$valueName) {
|
|
return [int]$sysValue.$valueName
|
|
}
|
|
}
|
|
|
|
# 3. Fallback to OS Default (Basic/Required)
|
|
return 1
|
|
}
|
|
|
|
Describe "Telemetry for shell startup" -Tag CI {
|
|
BeforeAll {
|
|
$skipTelemetryTests = $false
|
|
|
|
if ($IsWindows) {
|
|
## Skip telemetry tests if the OS telemetry level is less than 2 (Enhanced) -- PS telemetry is disabled in this case.
|
|
$osTelemetryLevel = Get-OSTelemetryLevel
|
|
$skipTelemetryTests = $osTelemetryLevel -lt 2
|
|
}
|
|
|
|
if ($skipTelemetryTests) {
|
|
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
|
|
$PSDefaultParameterValues["it:skip"] = $true
|
|
return
|
|
}
|
|
|
|
# if the telemetry file exists, move it out of the way
|
|
# the member is internal, but we can retrieve it via reflection
|
|
$cacheDir = [System.Management.Automation.Platform].GetField("CacheDirectory","NonPublic,Static").GetValue($null)
|
|
$uuidPath = Join-Path -Path $cacheDir -ChildPath telemetry.uuid
|
|
$uuidFileExists = Test-Path -Path $uuidPath
|
|
if ( $uuidFileExists ) {
|
|
$originalBytes = Get-Content -AsByteStream -Path $uuidPath
|
|
Rename-Item -Path $uuidPath -NewName "${uuidPath}.original"
|
|
}
|
|
|
|
$PWSH = (Get-Process -Id $PID).MainModule.FileName
|
|
$telemetrySet = Test-Path -Path env:POWERSHELL_TELEMETRY_OPTOUT
|
|
$SendingTelemetry = $env:POWERSHELL_TELEMETRY_OPTOUT
|
|
|
|
function Invoke-TelemetryTestProcess {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string[]] $Argument
|
|
)
|
|
|
|
$startInfo = [System.Diagnostics.ProcessStartInfo]::new()
|
|
$startInfo.FileName = $PWSH
|
|
$startInfo.UseShellExecute = $false
|
|
foreach ($argumentValue in $Argument) {
|
|
$startInfo.ArgumentList.Add($argumentValue)
|
|
}
|
|
|
|
return [System.Diagnostics.Process]::Start($startInfo)
|
|
}
|
|
}
|
|
|
|
AfterAll {
|
|
if ($skipTelemetryTests) {
|
|
$global:PSDefaultParameterValues = $originalDefaultParameterValues
|
|
return
|
|
}
|
|
|
|
# check and reset the telemetry.uuid file
|
|
if ( $uuidFileExists ) {
|
|
if ( Test-Path -Path "${uuidPath}.original" ) {
|
|
Rename-Item -NewName $uuidPath -Path "${uuidPath}.original" -Force
|
|
}
|
|
else {
|
|
[System.IO.File]::WriteAllBytes($uuidPath, $originalBytes)
|
|
}
|
|
}
|
|
if ( $telemetrySet ) {
|
|
$env:POWERSHELL_TELEMETRY_OPTOUT = $SendingTelemetry
|
|
}
|
|
}
|
|
|
|
AfterEach {
|
|
if ( Test-Path -Path $uuidPath ) {
|
|
Remove-Item -Path $uuidPath
|
|
}
|
|
if ( Test-Path -Path env:POWERSHELL_TELEMETRY_OPTOUT ) {
|
|
Remove-Item env:POWERSHELL_TELEMETRY_OPTOUT
|
|
}
|
|
}
|
|
|
|
It "Should not create a uuid file if telemetry is opted out" {
|
|
$env:POWERSHELL_TELEMETRY_OPTOUT = 1
|
|
& $PWSH -NoProfile -Command "exit"
|
|
$uuidPath | Should -Not -Exist
|
|
}
|
|
|
|
It "Should create a uuid file if telemetry is opted in" {
|
|
$env:POWERSHELL_TELEMETRY_OPTOUT = "no"
|
|
& $PWSH -NoProfile -Command "exit"
|
|
$uuidPath | Should -Exist
|
|
}
|
|
|
|
It "Should not block concurrent telemetry-enabled shell startup when the uuid file is missing" {
|
|
$env:POWERSHELL_TELEMETRY_OPTOUT = "no"
|
|
$holderReadyPath = Join-Path -Path $TestDrive -ChildPath "telemetry-holder.ready"
|
|
$holderStopPath = Join-Path -Path $TestDrive -ChildPath "telemetry-holder.stop"
|
|
$holderScriptPath = Join-Path -Path $TestDrive -ChildPath "telemetry-holder.ps1"
|
|
@'
|
|
param($ReadyPath, $StopPath)
|
|
|
|
[void][Microsoft.PowerShell.Telemetry.ApplicationInsightsTelemetry]::CanSendTelemetry
|
|
[System.IO.File]::WriteAllText($ReadyPath, "")
|
|
while (-not [System.IO.File]::Exists($StopPath)) {
|
|
Start-Sleep -Milliseconds 100
|
|
}
|
|
'@ | Set-Content -LiteralPath $holderScriptPath
|
|
|
|
$holderProcess = $null
|
|
$childProcess = $null
|
|
try {
|
|
$holderProcess = Invoke-TelemetryTestProcess -Argument @(
|
|
"-NoProfile",
|
|
"-File",
|
|
$holderScriptPath,
|
|
$holderReadyPath,
|
|
$holderStopPath
|
|
)
|
|
|
|
Wait-UntilTrue -sb { Test-Path -LiteralPath $holderReadyPath } -TimeoutInMilliseconds 15000 -IntervalInMilliseconds 100 |
|
|
Should -BeTrue
|
|
$uuidPath | Should -Exist
|
|
Remove-Item -LiteralPath $uuidPath
|
|
|
|
$childProcess = Invoke-TelemetryTestProcess -Argument @("-NoProfile", "-Command", "exit")
|
|
$childProcess.WaitForExit(15000) | Should -BeTrue
|
|
$childProcess.ExitCode | Should -Be 0
|
|
$uuidPath | Should -Exist
|
|
}
|
|
finally {
|
|
if ($childProcess -and -not $childProcess.HasExited) {
|
|
$childProcess.Kill($true)
|
|
$childProcess.WaitForExit()
|
|
}
|
|
|
|
if ($childProcess) {
|
|
$childProcess.Dispose()
|
|
}
|
|
|
|
[System.IO.File]::WriteAllText($holderStopPath, "")
|
|
if ($holderProcess -and -not $holderProcess.WaitForExit(5000)) {
|
|
$holderProcess.Kill($true)
|
|
$holderProcess.WaitForExit()
|
|
}
|
|
|
|
if ($holderProcess) {
|
|
$holderProcess.Dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
It "Should stop creating the uuid file when the telemetry mutex times out" {
|
|
$env:POWERSHELL_TELEMETRY_OPTOUT = "no"
|
|
$mutex = [System.Threading.Mutex]::new($false, "CreateUniqueUserId")
|
|
$mutexAcquired = $false
|
|
$childProcess = $null
|
|
try {
|
|
try {
|
|
$mutexAcquired = $mutex.WaitOne(5000)
|
|
}
|
|
catch [System.Threading.AbandonedMutexException] {
|
|
$mutexAcquired = $true
|
|
}
|
|
|
|
$mutexAcquired | Should -BeTrue -Because "the test must own the mutex to force the child to time out"
|
|
$childProcess = Invoke-TelemetryTestProcess -Argument @("-NoProfile", "-Command", "exit")
|
|
$childProcess.WaitForExit(15000) | Should -BeTrue
|
|
$childProcess.ExitCode | Should -Be 0
|
|
$uuidPath | Should -Not -Exist
|
|
}
|
|
finally {
|
|
if ($childProcess -and -not $childProcess.HasExited) {
|
|
$childProcess.Kill($true)
|
|
$childProcess.WaitForExit()
|
|
}
|
|
|
|
if ($childProcess) {
|
|
$childProcess.Dispose()
|
|
}
|
|
|
|
if ($mutexAcquired) {
|
|
$mutex.ReleaseMutex()
|
|
}
|
|
|
|
$mutex.Dispose()
|
|
}
|
|
}
|
|
|
|
It "Should create a uuid file by default" {
|
|
if ( Test-Path env:POWERSHELL_TELEMETRY_OPTOUT ) { Remove-Item -Path env:POWERSHELL_TELEMETRY_OPTOUT }
|
|
& $PWSH -NoProfile -Command "exit"
|
|
$uuidPath | Should -Exist
|
|
}
|
|
|
|
It "Should create a property uuid file when telemetry is sent" {
|
|
$env:POWERSHELL_TELEMETRY_OPTOUT = "no"
|
|
& $PWSH -NoProfile -Command "exit"
|
|
$uuidPath | Should -Exist
|
|
(Get-ChildItem -Path $uuidPath).Length | Should -Be 16
|
|
[byte[]]$newBytes = Get-Content -AsByteStream -Path $uuidPath
|
|
[System.Guid]::New($newBytes) | Should -BeOfType System.Guid
|
|
}
|
|
|
|
It "Should not create a telemetry file if one already exists and telemetry is opted in" {
|
|
[byte[]]$bytes = [System.Guid]::NewGuid().ToByteArray()
|
|
[System.IO.File]::WriteAllBytes($uuidPath, $bytes)
|
|
& $PWSH -NoProfile -Command "exit"
|
|
[byte[]]$newBytes = Get-Content -AsByteStream -Path $uuidPath
|
|
Compare-Object -ReferenceObject $bytes -DifferenceObject $newBytes | Should -BeNullOrEmpty
|
|
}
|
|
|
|
It "Should create a new telemetry file if the current one is 00000000-0000-0000-0000-000000000000" {
|
|
[byte[]]$zeroGuid = [System.Guid]::Empty.ToByteArray()
|
|
[System.IO.File]::WriteAllBytes($uuidPath, $zeroGuid)
|
|
& $PWSH -NoProfile -Command "exit"
|
|
[byte[]]$newBytes = Get-Content -AsByteStream -Path $uuidPath
|
|
# we could legitimately have zeros in the new guid, so we can't check for that
|
|
# we're just making sure that there *is* a difference
|
|
Compare-Object -ReferenceObject $zeroGuid -DifferenceObject $newBytes | Should -Not -BeNullOrEmpty
|
|
}
|
|
|
|
It "Should create a new telemetry file if the current one is smaller than 16 bytes" {
|
|
$badBytes = [byte[]]::new(8);
|
|
[System.IO.File]::WriteAllBytes($uuidPath, $badBytes)
|
|
& $PWSH -NoProfile -Command "exit"
|
|
[byte[]]$nb = Get-Content -AsByteStream -Path $uuidPath
|
|
[System.Guid]::New($nb) | Should -BeOfType System.Guid
|
|
}
|
|
|
|
It "Should not create a new telemetry file if the current one has a valid guid and is larger than 16 bytes" {
|
|
$g = [Guid]::newGuid()
|
|
$tooManyBytes = $g.ToByteArray() * 2
|
|
[System.IO.File]::WriteAllBytes($uuidPath, $tooManyBytes)
|
|
[byte[]]$nb = Get-Content -Path $uuidPath -AsByteStream | Select-Object -First 16
|
|
$ng = [System.Guid]::new($nb)
|
|
$g | Should -Be $ng
|
|
}
|
|
|
|
It "Should properly set whether telemetry is sent based on when environment variable is not set" {
|
|
$result = & $PWSH -NoProfile -Command '[Microsoft.PowerShell.Telemetry.ApplicationInsightsTelemetry]::CanSendTelemetry'
|
|
$result | Should -Be "True"
|
|
}
|
|
|
|
$telemetryIsSetData = @(
|
|
@{ name = "set to no"; Value = "no" ; expectedValue = "True" }
|
|
@{ name = "set to 0"; Value = "0"; expectedValue = "True" }
|
|
@{ name = "set to false"; Value = "false"; expectedValue = "True" }
|
|
@{ name = "set to yes"; Value = "yes"; expectedValue = "False" }
|
|
@{ name = "set to 1"; Value = "1"; expectedValue = "False" }
|
|
@{ name = "set to true"; Value = "true"; expectedValue = "False" }
|
|
)
|
|
|
|
It "Should properly set whether telemetry is sent based on environment variable when <name>" -TestCases $telemetryIsSetData {
|
|
param ( [string]$name, [string]$value, [string]$expectedValue )
|
|
$env:POWERSHELL_TELEMETRY_OPTOUT = $value
|
|
$result = & $PWSH -NoProfile -Command '[Microsoft.PowerShell.Telemetry.ApplicationInsightsTelemetry]::CanSendTelemetry'
|
|
$result | Should -Be $expectedValue
|
|
}
|
|
|
|
It "Should send startup event" {
|
|
$resultJson = & $PWSH -NoProfile -c {
|
|
# this should ensure that the startup telemetry event is sent.
|
|
$null = Get-Date | Out-String
|
|
$telemetryType = [Microsoft.PowerShell.Telemetry.ApplicationInsightsTelemetry]
|
|
$bindingFlags = [System.Reflection.BindingFlags]"NonPublic,Static"
|
|
$observedValue = ${telemetryType}.GetMember("s_startupEventSent", $bindingFlags)[0].GetValue($null)
|
|
$observedValue | Should -Be 1 -Because "Should have sent telemetry on console startup"
|
|
}
|
|
}
|
|
}
|