Add typed native WEC runtime observations (#424)

* Observe typed native WEC subscription runtime status

* Link typed WEC runtime changelog to PR 424

* Pass a native null source for subscription runtime queries

* Ignore unused count storage for native null WEC variants

* Keep unavailable WEC source inventories unknown and diagnose native XML reads

* Read native WEC subscription XML with bounded explicit Unicode pipes

* Use bounded Unicode subscription reads in runtime observations and cleanup

* Decode native WEC XML BOMs without unsupported Unicode switch

* Describe strict native WEC XML byte decoding

* Reference System.Xml explicitly when compiling under Windows PowerShell

* Regenerate website changelog snapshots with their proper headers
This commit is contained in:
田中ザック Isaac Mathis authored and GitHub committed 2026-09-20 22:48:32 +09:00
1 parent 5ba53fbcfb
commit 913b1dfaee
19 files changed
+650 -5

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
// Bounded native XML bytes, decoded independently of the PowerShell console code page.
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
namespace Wela.WecXml {
public static class Reader {
static async Task<byte[]> ReadBounded(Stream stream,int maximum) {
using(MemoryStream output=new MemoryStream()) {
byte[] buffer=new byte[8192];int count;
while((count=await stream.ReadAsync(buffer,0,buffer.Length).ConfigureAwait(false))!=0) {
if(output.Length+count>maximum)throw new IOException("Native WEC output exceeds its byte limit.");
output.Write(buffer,0,count);
}
return output.ToArray();
}
}
public static string DecodeXml(byte[] bytes) {
if(bytes==null||bytes.Length==0||bytes.Length>10485760)throw new InvalidDataException("Expected bounded native WEC XML bytes.");
int skip=0;Encoding encoding=new UTF8Encoding(false,true);
if(bytes.Length>=3&&bytes[0]==239&&bytes[1]==187&&bytes[2]==191){skip=3;}
else if(bytes.Length>=2&&bytes[0]==255&&bytes[1]==254){skip=2;encoding=new UnicodeEncoding(false,false,true);}
else if(bytes.Length>=2&&bytes[0]==254&&bytes[1]==255){skip=2;encoding=new UnicodeEncoding(true,false,true);}
else if(bytes.Length>=4&&bytes[0]==60&&bytes[1]==0){encoding=new UnicodeEncoding(false,false,true);}
else if(bytes.Length>=4&&bytes[0]==0&&bytes[1]==60){encoding=new UnicodeEncoding(true,false,true);}
string text=encoding.GetString(bytes,skip,bytes.Length-skip);
XmlReaderSettings settings=new XmlReaderSettings();settings.DtdProcessing=DtdProcessing.Prohibit;settings.XmlResolver=null;settings.MaxCharactersInDocument=10485760;
using(XmlReader reader=XmlReader.Create(new StringReader(text),settings)){while(reader.Read()){};}
return text;
}
public static string ReadXml(string id) {
if(id==null||!Regex.IsMatch(id,@"\A[A-Za-z0-9][A-Za-z0-9 ._-]{0,127}\z"))throw new ArgumentException("Select one exact supported subscription ID.");
ProcessStartInfo start=new ProcessStartInfo();start.FileName=Path.Combine(Environment.SystemDirectory,"wecutil.exe");
start.Arguments="gs \""+id+"\" /f:xml";start.UseShellExecute=false;start.CreateNoWindow=true;start.RedirectStandardOutput=true;start.RedirectStandardError=true;
using(Process process=new Process()) {
process.StartInfo=start;bool started=false;
try {
started=process.Start();if(!started)throw new IOException("Native WEC process did not start.");
Stopwatch timer=Stopwatch.StartNew();Task<byte[]> output=ReadBounded(process.StandardOutput.BaseStream,10485760);Task<byte[]> error=ReadBounded(process.StandardError.BaseStream,65536);
while(!process.WaitForExit(100)) {
if(output.IsFaulted||error.IsFaulted)throw new IOException("Native WEC output could not be read within its bounds.");
if(timer.ElapsedMilliseconds>=30000)throw new TimeoutException("Native WEC definition read exceeded thirty seconds.");
}
int remaining=Math.Max(1,30000-(int)timer.ElapsedMilliseconds);
if(!Task.WaitAll(new Task[]{output,error},remaining))throw new TimeoutException("Native WEC output did not complete within thirty seconds.");
if(process.ExitCode!=0)throw new IOException("Native WEC definition read failed with exit code "+process.ExitCode+"; no definition was accepted.");
if(error.Result.Length!=0)throw new IOException("Native WEC returned unexpected diagnostic bytes; no definition was accepted.");
return DecodeXml(output.Result);
} finally {
if(started){try{if(!process.HasExited){process.Kill();process.WaitForExit(1000);}}catch(InvalidOperationException){}}
}
}
}
}
}
+14 -1
View File
@@ -159,4 +159,17 @@ function Import-WelaWefConfig {
[pscustomobject]@{ Config=$config; Path=$full; Subscriptions=$subscriptions }
}
Export-ModuleMember -Function Read-WelaWefXml, Get-WelaWefXmlKey, ConvertFrom-WelaWefQuery, Get-WelaWefAuthorization, ConvertFrom-WelaWefSubscription, Import-WelaWefConfig
function Read-WelaWecSubscriptionXml {
param([Parameter(Mandatory)][string]$Id)
if([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not [Environment]::Is64BitProcess){throw 'Native WEC XML reads require 64-bit Windows.'}
$path=Join-Path $PSScriptRoot 'WecSubscriptionXml.cs';$hash=(Get-FileHash -LiteralPath $path -Algorithm SHA256 -ErrorAction Stop).Hash
if(-not ('Wela.WecXml.Reader' -as [type])){
$compile=@{Path=$path;ErrorAction='Stop'}
if($PSVersionTable.PSEdition -eq 'Desktop'){$compile.ReferencedAssemblies=@('System.dll','System.Core.dll','System.Xml.dll')}
Add-Type @compile;$script:WelaWecXmlSourceHash=$hash
}
if($script:WelaWecXmlSourceHash -cne $hash){throw 'Loaded native WEC XML reader differs from its source; start a fresh session.'}
[Wela.WecXml.Reader]::ReadXml($Id)
}
Export-ModuleMember -Function Read-WelaWecSubscriptionXml, Read-WelaWefXml, Get-WelaWefXmlKey, ConvertFrom-WelaWefQuery, Get-WelaWefAuthorization, ConvertFrom-WelaWefSubscription, Import-WelaWefConfig