336 lines
11 KiB
PowerShell
336 lines
11 KiB
PowerShell
$ErrorActionPreference = "Stop"
|
|
|
|
$root = Split-Path $PSScriptRoot -Parent
|
|
$scriptPath = Join-Path $root "Upgrade-MinerOpsAgentCredential.ps1"
|
|
. $scriptPath
|
|
|
|
$script:Passed = 0
|
|
$script:BootEventRecords = @()
|
|
$script:ToXmlCalls = 0
|
|
|
|
function Assert-True {
|
|
param(
|
|
[Parameter(Mandatory)][bool]$Condition,
|
|
[Parameter(Mandatory)][string]$Message
|
|
)
|
|
if (-not $Condition) { throw $Message }
|
|
}
|
|
|
|
function Assert-Throws {
|
|
param(
|
|
[Parameter(Mandatory)][scriptblock]$Action,
|
|
[string]$Pattern = ""
|
|
)
|
|
try { & $Action | Out-Null }
|
|
catch {
|
|
if ($Pattern -and $_.Exception.Message -notmatch $Pattern) {
|
|
throw "Expected error '$Pattern', got '$($_.Exception.Message)'."
|
|
}
|
|
return
|
|
}
|
|
throw "Expected action to fail."
|
|
}
|
|
|
|
function Invoke-ParserTest {
|
|
param(
|
|
[Parameter(Mandatory)][string]$Name,
|
|
[Parameter(Mandatory)][scriptblock]$Action
|
|
)
|
|
& $Action
|
|
$script:Passed++
|
|
Write-Host "PASS: $Name"
|
|
}
|
|
|
|
function New-TestKernelBootXml {
|
|
param(
|
|
[string]$Namespace =
|
|
"http://schemas.microsoft.com/win/2004/08/events/event",
|
|
[string]$BootTypeContent = '<Data Name="BootType">0</Data>',
|
|
[switch]$OmitSystem,
|
|
[switch]$OmitEventData,
|
|
[switch]$DuplicateEventData
|
|
)
|
|
|
|
$system = if ($OmitSystem) { "" } else {
|
|
@"
|
|
<System>
|
|
<Provider Name="Microsoft-Windows-Kernel-Boot" />
|
|
<EventID>27</EventID>
|
|
<Version>1</Version>
|
|
<Level>4</Level>
|
|
<Task>33</Task>
|
|
<Opcode>0</Opcode>
|
|
<EventRecordID>42</EventRecordID>
|
|
<Channel>System</Channel>
|
|
</System>
|
|
"@
|
|
}
|
|
$eventData = if ($OmitEventData) { "" } else {
|
|
" <EventData>$BootTypeContent</EventData>"
|
|
}
|
|
if ($DuplicateEventData) {
|
|
$eventData += "`n <EventData>$BootTypeContent</EventData>"
|
|
}
|
|
return "<Event xmlns=`"$Namespace`">`n$system$eventData`n</Event>"
|
|
}
|
|
|
|
function New-TestEventRecord {
|
|
param(
|
|
[AllowNull()]$ProviderName = "Microsoft-Windows-Kernel-Boot",
|
|
[AllowNull()]$Id = [int]27,
|
|
[AllowNull()]$RecordId = [long]42,
|
|
[AllowNull()]$Version = [byte]1,
|
|
[AllowNull()]$Xml = (New-TestKernelBootXml)
|
|
)
|
|
|
|
$record = [pscustomobject]@{
|
|
ProviderName = $ProviderName
|
|
Id = $Id
|
|
RecordId = $RecordId
|
|
Version = $Version
|
|
XmlPayload = $Xml
|
|
}
|
|
$record | Add-Member -MemberType ScriptMethod -Name ToXml -Value {
|
|
$script:ToXmlCalls++
|
|
return $this.XmlPayload
|
|
}
|
|
return $record
|
|
}
|
|
|
|
function Get-WinEvent {
|
|
param($FilterHashtable, $ErrorAction)
|
|
return $script:BootEventRecords
|
|
}
|
|
|
|
Invoke-ParserTest "conventional namespaced Event XML and typed metadata parse" {
|
|
$parsed = ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord)
|
|
Assert-True ($parsed.ProviderName -ceq
|
|
"Microsoft-Windows-Kernel-Boot") "Typed provider was not retained."
|
|
Assert-True ($parsed.EventId -is [int] -and $parsed.EventId -eq 27) `
|
|
"Typed event ID was not retained."
|
|
Assert-True ($parsed.RecordId -is [long] -and $parsed.RecordId -eq 42) `
|
|
"Typed record ID was not retained."
|
|
Assert-True ($parsed.Version -is [byte] -and $parsed.Version -eq 1) `
|
|
"Typed event version was not retained."
|
|
Assert-True ($parsed.BootType -is [UInt32] -and $parsed.BootType -eq 0) `
|
|
"BootType payload was not parsed."
|
|
}
|
|
|
|
Invoke-ParserTest "supported event version zero parses" {
|
|
$parsed = ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord -Version ([byte]0))
|
|
Assert-True ($parsed.Version -eq 0) "Version zero was not accepted."
|
|
}
|
|
|
|
Invoke-ParserTest "production Get-WinEvent path invokes ToXml parser" {
|
|
$script:ToXmlCalls = 0
|
|
$script:BootEventRecords = @(
|
|
New-TestEventRecord -RecordId ([long]43)
|
|
)
|
|
$identifier = Get-MinerOpsBootIdentifier
|
|
Assert-True ($identifier -ceq
|
|
"windows-full-boot-event-v1:000000000000002b") `
|
|
"Production event path returned the wrong identifier."
|
|
Assert-True ($script:ToXmlCalls -eq 1) `
|
|
"Production event path did not invoke ToXml exactly once."
|
|
}
|
|
|
|
Invoke-ParserTest "typed metadata is authoritative over XML scalar metadata" {
|
|
$xml = (New-TestKernelBootXml).Replace(
|
|
"<EventID>27</EventID>", "<EventID>999</EventID>"
|
|
).Replace(
|
|
"<EventRecordID>42</EventRecordID>",
|
|
"<EventRecordID>999</EventRecordID>"
|
|
)
|
|
$parsed = ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord -Xml $xml)
|
|
Assert-True ($parsed.EventId -eq 27 -and $parsed.RecordId -eq 42) `
|
|
"XML scalar metadata overrode typed EventRecord properties."
|
|
}
|
|
|
|
Invoke-ParserTest "null typed RecordId fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord -RecordId $null)
|
|
} "metadata"
|
|
}
|
|
|
|
Invoke-ParserTest "wrong typed provider fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -ProviderName "Unexpected-Provider")
|
|
} "metadata"
|
|
}
|
|
|
|
Invoke-ParserTest "wrong typed event ID fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord -Id ([int]28))
|
|
} "metadata"
|
|
}
|
|
|
|
Invoke-ParserTest "wrong RecordId type fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord -RecordId ([UInt64]42))
|
|
} "metadata"
|
|
}
|
|
|
|
Invoke-ParserTest "null event version fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord -Version $null)
|
|
} "metadata"
|
|
}
|
|
|
|
Invoke-ParserTest "unsupported event version fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord -Version ([byte]2))
|
|
} "unsupported"
|
|
}
|
|
|
|
Invoke-ParserTest "unexpected Event XML namespace fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml `
|
|
-Namespace "urn:unexpected"))
|
|
} "root"
|
|
}
|
|
|
|
Invoke-ParserTest "malformed Event XML fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord `
|
|
-EventRecord (New-TestEventRecord -Xml "<Event>")
|
|
} "malformed"
|
|
}
|
|
|
|
Invoke-ParserTest "missing System element fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml -OmitSystem))
|
|
} "schema"
|
|
}
|
|
|
|
Invoke-ParserTest "missing EventData fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml -OmitEventData))
|
|
} "schema"
|
|
}
|
|
|
|
Invoke-ParserTest "duplicated EventData fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml `
|
|
(New-TestKernelBootXml -DuplicateEventData))
|
|
} "schema"
|
|
}
|
|
|
|
Invoke-ParserTest "missing BootType fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml `
|
|
-BootTypeContent '<Data Name="LoadOptions"></Data>'))
|
|
} "schema"
|
|
}
|
|
|
|
Invoke-ParserTest "duplicate BootType fails closed" {
|
|
$payload = '<Data Name="BootType">0</Data>' +
|
|
'<Data Name="BootType">0</Data>'
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml `
|
|
-BootTypeContent $payload))
|
|
} "ambiguous"
|
|
}
|
|
|
|
Invoke-ParserTest "nonnumeric BootType fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml `
|
|
-BootTypeContent '<Data Name="BootType">full</Data>'))
|
|
} "canonical"
|
|
}
|
|
|
|
Invoke-ParserTest "noncanonical BootType fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml `
|
|
-BootTypeContent '<Data Name="BootType">00</Data>'))
|
|
} "canonical"
|
|
}
|
|
|
|
Invoke-ParserTest "structured BootType content fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml `
|
|
-BootTypeContent `
|
|
'<Data Name="BootType"><Value>0</Value></Data>'))
|
|
} "schema"
|
|
}
|
|
|
|
Invoke-ParserTest "BootType outside UInt32 fails closed" {
|
|
Assert-Throws {
|
|
ConvertFrom-MinerOpsKernelBootEventRecord -EventRecord `
|
|
(New-TestEventRecord -Xml (New-TestKernelBootXml `
|
|
-BootTypeContent `
|
|
'<Data Name="BootType">4294967296</Data>'))
|
|
} "invalid"
|
|
}
|
|
|
|
Invoke-ParserTest "nonzero BootType is not a full-boot anchor" {
|
|
$script:BootEventRecords = @(
|
|
New-TestEventRecord -Xml (New-TestKernelBootXml `
|
|
-BootTypeContent '<Data Name="BootType">1</Data>')
|
|
)
|
|
Assert-Throws { Get-MinerOpsBootIdentifier } "could not be verified"
|
|
}
|
|
|
|
Invoke-ParserTest "empty event query fails closed" {
|
|
$script:BootEventRecords = @()
|
|
Assert-Throws { Get-MinerOpsBootIdentifier } "could not be verified"
|
|
}
|
|
|
|
Invoke-ParserTest "duplicate qualifying record IDs fail closed" {
|
|
$script:BootEventRecords = @(
|
|
New-TestEventRecord -RecordId ([long]42)
|
|
New-TestEventRecord -RecordId ([long]43)
|
|
New-TestEventRecord -RecordId ([long]42)
|
|
)
|
|
Assert-Throws { Get-MinerOpsBootIdentifier } "could not be verified"
|
|
}
|
|
|
|
Invoke-ParserTest "equal stored and current anchors deny reset" {
|
|
Assert-Throws {
|
|
Assert-MinerOpsBootEpochChanged `
|
|
-StoredBootIdentifier "windows-full-boot-event-v1:000000000000002a" `
|
|
-CurrentBootIdentifier "windows-full-boot-event-v1:000000000000002a"
|
|
} "without a verified full Restart"
|
|
}
|
|
|
|
Invoke-ParserTest "strictly greater current anchor passes ordering" {
|
|
Assert-MinerOpsBootEpochChanged `
|
|
-StoredBootIdentifier "windows-full-boot-event-v1:000000000000002a" `
|
|
-CurrentBootIdentifier "windows-full-boot-event-v1:000000000000002b"
|
|
}
|
|
|
|
Invoke-ParserTest "smaller current anchor fails closed" {
|
|
Assert-Throws {
|
|
Assert-MinerOpsBootEpochChanged `
|
|
-StoredBootIdentifier "windows-full-boot-event-v1:000000000000002a" `
|
|
-CurrentBootIdentifier "windows-full-boot-event-v1:0000000000000029"
|
|
} "older than the"
|
|
}
|
|
|
|
Invoke-ParserTest "malformed stored anchor fails closed" {
|
|
Assert-Throws {
|
|
Assert-MinerOpsBootEpochChanged `
|
|
-StoredBootIdentifier "windows-full-boot-event-v1:not-a-record" `
|
|
-CurrentBootIdentifier "windows-full-boot-event-v1:000000000000002b"
|
|
} "canonical"
|
|
}
|
|
|
|
Write-Host "$script:Passed boot-event parser tests passed."
|