Skip to content

Instantly share code, notes, and snippets.

@nohwnd
Created September 21, 2020 13:09
Show Gist options
  • Save nohwnd/670aa06b989c826c22fab86742a1d6c6 to your computer and use it in GitHub Desktop.
Save nohwnd/670aa06b989c826c22fab86742a1d6c6 to your computer and use it in GitHub Desktop.
Mocking static property getter using Harmony
$harmony = "$PSScriptRoot/0Harmony.dll"
Import-Module $harmony
$Script:Patches = @()
function Set-StaticPropertyGetter {
param (
[Parameter(Mandatory)]
[Type] $Type,
[Parameter(Mandatory)]
[string] $PropertyName,
[Parameter(Mandatory)]
$Value
)
$property = $Type.GetProperty($PropertyName, [System.Reflection.BindingFlags]"Public, Static")
$get = $property.GetAccessors() | where { $_.Name -like "get_*" }
$fullTypeName = $Type.FullName
if (-not $get) {
throw "No public get accessor is defined for $fullTypeName.$PropertyName"
}
$dll = [IO.Path]::ChangeExtension([IO.Path]::GetTempPath(), ".dll");
$fakeTypeName = "Fake_$($fullTypeName -replace "\.","_")"
$code = @"
using HarmonyLib;
using System.Reflection;
using System.Linq;
public static class $fakeTypeName
{
private static Harmony _harmony;
public static $fullTypeName Return { get; set; }
public static MethodInfo Getter { get; set; }
public static bool Action (ref $fullTypeName __result)
{
__result = Return;
return false;
}
public static void Patch()
{
var prefix = typeof($fakeTypeName).GetMethod("Action", BindingFlags.Static | BindingFlags.Public);
_harmony = new Harmony("$fullTypeName.$PropertyName");
_harmony.Patch(Getter, new HarmonyMethod(prefix));
}
public static void Unpatch() {
_harmony.UnpatchAll();
}
}
"@
$d = Add-Type -TypeDefinition $code -ReferencedAssemblies $harmony -PassThru -ErrorAction Stop
$t = [Type]$fakeTypeName
$t::Getter = $get
$t::Return = [DateTime]::MaxValue
$t::Patch()
$script:Patches += $t
}
if ([datetime]::Now -eq [DateTime]::MaxValue) {
throw "already patched"
}
try {
Set-StaticPropertyGetter -Type ([datetime]) -PropertyName "Now" -Value [DateTime]::MaxValue
$now = [datetime]::now
if ($now -ne [datetime]::MaxValue) {
throw "Faking did not work. DateTime.Now did not return Max value, it returned '$now' instead."
}
else {
Write-Host -ForegroundColor Green "Success [DateTime]::Now returned '$now'!"
}
}
finally {
foreach ($p in $script:Patches) {
$p::Unpatch()
}
}
@nohwnd
Copy link
Author

nohwnd commented Sep 23, 2020

few bugs in the above code, I am not passing $Value, and the return type should not the the same as the source type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment