94 lines
5.2 KiB
PowerShell
94 lines
5.2 KiB
PowerShell
function Get-OfflineImageState {
|
|
param([Parameter(Mandatory=$true)][string]$HivePath)
|
|
if (-not ('PveOffline.HiveFileReader' -as [type])) {
|
|
Add-Type -TypeDefinition @"
|
|
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
namespace PveOffline {
|
|
public sealed class HiveFileReader {
|
|
private readonly byte[] data;
|
|
private HiveFileReader(string file) {
|
|
var info = new FileInfo(file);
|
|
if (info.Length < 4096 || info.Length > 512L*1024*1024) throw new InvalidDataException("Unsupported registry hive size.");
|
|
data = File.ReadAllBytes(file);
|
|
if (Encoding.ASCII.GetString(data, 0, 4) != "regf") throw new InvalidDataException("Not a registry hive.");
|
|
if (U32(20) != 1 || U32(24) > 6) throw new InvalidDataException("Unsupported registry hive format.");
|
|
}
|
|
private void Bounds(int at, int size) {
|
|
if (at < 0 || size < 0 || at > data.Length-size) throw new InvalidDataException("Registry cell is outside the hive.");
|
|
}
|
|
private uint U32(int at) { Bounds(at,4); return BitConverter.ToUInt32(data,at); }
|
|
private ushort U16(int at) { Bounds(at,2); return BitConverter.ToUInt16(data,at); }
|
|
private int Cell(uint relative) {
|
|
long at = 4096L + relative;
|
|
if (at > int.MaxValue) throw new InvalidDataException("Invalid registry cell offset.");
|
|
int offset = (int)at;
|
|
Bounds(offset,4);
|
|
int size = BitConverter.ToInt32(data,offset);
|
|
if (size >= -4 || size == int.MinValue) throw new InvalidDataException("Registry cell is not allocated.");
|
|
Bounds(offset,-size);
|
|
return offset+4;
|
|
}
|
|
private string Signature(int at) { Bounds(at,2); return Encoding.ASCII.GetString(data,at,2); }
|
|
private string Name(int at, int size, bool ascii) {
|
|
Bounds(at,size);
|
|
return (ascii ? Encoding.GetEncoding(1252) : Encoding.Unicode).GetString(data,at,size);
|
|
}
|
|
private string KeyName(int at) {
|
|
if (Signature(at) != "nk") throw new InvalidDataException("Expected a registry key cell.");
|
|
return Name(at+76,U16(at+72),(U16(at+2)&0x20)!=0);
|
|
}
|
|
private IEnumerable<uint> Subkeys(uint index, int depth) {
|
|
if (depth > 32) throw new InvalidDataException("Registry subkey index is too deep.");
|
|
int at=Cell(index); string kind=Signature(at); int count=U16(at+2);
|
|
if (kind=="lf" || kind=="lh") {
|
|
Bounds(at+4,count*8);
|
|
for(int i=0;i<count;i++) yield return U32(at+4+i*8);
|
|
} else if(kind=="li" || kind=="ri") {
|
|
Bounds(at+4,count*4);
|
|
for(int i=0;i<count;i++) {
|
|
uint next=U32(at+4+i*4);
|
|
if(kind=="li") yield return next;
|
|
else foreach(uint child in Subkeys(next,depth+1)) yield return child;
|
|
}
|
|
} else throw new InvalidDataException("Unsupported registry subkey index.");
|
|
}
|
|
private uint FindChild(uint parent,string name) {
|
|
int at=Cell(parent);
|
|
if (Signature(at)!="nk" || U32(at+20)==0) throw new InvalidDataException("Offline registry key is missing: "+name);
|
|
foreach(uint child in Subkeys(U32(at+28),0))
|
|
if(string.Equals(KeyName(Cell(child)),name,StringComparison.OrdinalIgnoreCase)) return child;
|
|
throw new InvalidDataException("Offline registry key is missing: "+name);
|
|
}
|
|
private string StringValue(uint key,string name) {
|
|
int at=Cell(key); uint count=U32(at+36);
|
|
if(count>100000) throw new InvalidDataException("Invalid registry value count.");
|
|
int values=Cell(U32(at+40)); Bounds(values,checked((int)count*4));
|
|
for(int i=0;i<(int)count;i++) {
|
|
int value=Cell(U32(values+i*4));
|
|
if(Signature(value)!="vk") throw new InvalidDataException("Invalid registry value cell.");
|
|
string valueName=Name(value+20,U16(value+2),(U16(value+16)&1)!=0);
|
|
if(!string.Equals(valueName,name,StringComparison.OrdinalIgnoreCase)) continue;
|
|
if(U32(value+12)!=1) throw new InvalidDataException("Expected a REG_SZ registry value.");
|
|
uint length=U32(value+4); bool inline=(length&0x80000000)!=0; length&=0x7fffffff;
|
|
if(length>4096 || (inline && length>4) || length%2!=0) throw new InvalidDataException("Invalid registry string size.");
|
|
int text=inline ? value+8 : Cell(U32(value+8));
|
|
return Name(text,(int)length,false).TrimEnd((char)0);
|
|
}
|
|
throw new InvalidDataException("Offline registry value is missing: "+name);
|
|
}
|
|
public static string ReadImageState(string file) {
|
|
var hive=new HiveFileReader(file);
|
|
uint key=hive.U32(36);
|
|
foreach(string segment in new[]{"Microsoft","Windows","CurrentVersion","Setup","State"}) key=hive.FindChild(key,segment);
|
|
return hive.StringValue(key,"ImageState");
|
|
}
|
|
}
|
|
}
|
|
"@
|
|
}
|
|
[PveOffline.HiveFileReader]::ReadImageState([IO.Path]::GetFullPath($HivePath))
|
|
}
|